Next.js
React controlled input이 유용했던 경우와 그렇지 않은 경우
모든 폼 필드를 controlled로 만들 필요는 없다. 언제 controlled가 필요한지 알면 코드가 단순해진다.
React 폼을 처음 배울 때 useState로 값을 관리하는 controlled input 패턴을 기본으로 익힌다. 그런데 무조건 모든 입력 필드를 controlled로 만들다 보면 불필요하게 복잡해지는 경우가 있다.
controlled와 uncontrolled의 차이
controlled input은 React 상태가 입력값의 단일 진실 공급원(source of truth)이 된다.
const [name, setName] = useState('')
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
uncontrolled input은 DOM이 값을 관리하고, 필요할 때 ref로 꺼낸다.
const nameRef = useRef<HTMLInputElement>(null)
<input ref={nameRef} />
// 제출 시점에
const value = nameRef.current?.value
controlled가 필요한 경우
실시간 유효성 검증. 비밀번호가 8자 미만이면 버튼을 비활성화하거나, 입력하는 도중에 에러 메시지를 보여주려면 상태가 필요하다. DOM에서 값을 꺼내는 uncontrolled 방식으로는 실시간 반응이 어렵다.
의존 필드. 비밀번호 확인 필드가 비밀번호 필드 값을 알아야 검증할 수 있는 경우처럼, 필드 간에 상태를 공유해야 할 때 controlled가 자연스럽다.
입력값 포맷팅. 전화번호를 010-1234-5678 형식으로 자동 포맷하거나, 숫자만 허용하면서 화면에는 천 단위 콤마를 표시하는 경우처럼 입력과 표시값을 분리해야 할 때 controlled가 필요하다.
const [phone, setPhone] = useState('')
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const digits = e.target.value.replace(/\D/g, '')
const formatted = digits.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')
setPhone(formatted)
}
폼 초기화. 제출 후 폼을 비우거나, 외부 버튼으로 폼 값을 리셋해야 한다면 상태로 관리하는 게 훨씬 간단하다. setName('') 한 줄로 끝난다.
uncontrolled가 더 나은 경우
파일 입력. <input type="file">은 보안 정책상 값을 React 상태로 직접 제어할 수 없다. ref로 접근하는 수밖에 없다.
단순한 단발성 폼. 값을 실시간으로 감시할 필요가 없고 제출 시점에만 값이 필요하다면 ref나 FormData가 더 간결하다.
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const data = new FormData(e.currentTarget)
const name = data.get('name') as string
}
<form onSubmit={handleSubmit}>
<input name="name" />
<button type="submit">제출</button>
</form>
React Hook Form 같은 라이브러리도 기본적으로 uncontrolled 방식으로 동작하면서 유효성 검증까지 처리한다. 필드가 많은 복잡한 폼에서는 controlled 방식보다 렌더링 횟수가 줄어서 성능이 낫다.