무엇
React의 <input>은 브라우저 입력 요소를 그대로 쓰되, 값을 상태에 묶는 두 가지 방식을 제공한다. 상태로 값을 쥐면 controlled, 초기값만 주고 나머지는 DOM에 맡기면 uncontrolled다.
controlled 입력은 value와 onChange를 함께 준다. React가 입력 값을 항상 상태와 일치하도록 강제한다.
const [firstName, setFirstName] = useState('')
<input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
uncontrolled 입력은 초기값만 준다. 텍스트는 defaultValue, 체크박스·라디오는 defaultChecked를 쓴다.
<input defaultValue="Taylor" />
<input type="checkbox" defaultChecked />
무슨 일이 일어나나
controlled 입력에서는 화면에 보이는 값이 곧 상태 값이다. 사용자가 키를 누르면 onChange가 불리고, 거기서 상태를 갱신해야 다음 렌더에 그 글자가 반영된다. onChange 없이 value만 주면 값이 상태에 고정돼 타이핑이 먹히지 않고, React가 경고를 낸다.
체크박스·라디오는 값이 아니라 켜짐 여부라, value/defaultValue가 아니라 checked/defaultChecked를 쓴다. onChange에서도 e.target.value가 아니라 e.target.checked를 읽는다.
한 입력은 controlled와 uncontrolled를 생애 중에 바꿀 수 없다. value가 처음엔 undefined였다가 나중에 문자열이 되면 React가 전환으로 보고 경고한다. controlled로 쓸 값은 null·undefined가 되지 않게, 빈 값은 빈 문자열 ''로 초기화한다.
폼 안의 입력은 name 속성으로 폼 데이터에 실린다. 지정한 name이 FormData의 키가 된다.
사용법
controlled 텍스트 입력의 기본형은 상태 + value + onChange 세 짝이다. onChange에서 값을 가공하지 말고 e.target.value를 그대로 넘긴다. 대문자 변환 같은 가공을 끼우면 커서가 맨 앞으로 튀는 문제가 생긴다.
import { useState } from 'react'
export function NameField() {
const [name, setName] = useState('')
return (
<label>
이름:
<input
name="firstName"
value={name}
onChange={(e) => setName(e.target.value)} // 가공 없이 그대로
/>
</label>
)
}
실무 예시
uncontrolled 입력만으로도 폼은 충분히 동작한다. 각 입력에 name만 주면 제출 때 FormData로 한꺼번에 읽을 수 있어, 상태를 필드마다 둘 필요가 없다.
export function ProfileForm() {
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const data = Object.fromEntries(formData.entries())
// { firstName: 'Taylor', subscribe: 'on' }
console.log(data)
}
return (
<form onSubmit={handleSubmit}>
<input name="firstName" defaultValue="Taylor" />
<input type="checkbox" name="subscribe" defaultChecked />
<button type="submit">저장</button>
</form>
)
}
React 19에서는 type="submit"·type="image" 입력에 formAction을 걸어, 그 버튼으로 제출할 때 폼의 action을 덮어쓸 수 있다.
왜 중요한가
입력은 거의 모든 폼의 최소 단위이고, controlled냐 uncontrolled냐의 선택이 그 폼의 상태 구조를 결정한다. 값을 실시간으로 검증·가공해야 하면 controlled가, 제출 때 한 번만 읽으면 되면 uncontrolled가 단순하다. React 19의 함수 action 흐름은 uncontrolled + name 조합과 잘 맞아서, 예전만큼 모든 입력을 상태로 쥐지 않아도 되는 경우가 늘었다. 자주 쓰는 요소인 만큼 두 방식의 경계와 controlled/uncontrolled 경고의 원인을 아는 게 실질적으로 도움이 된다.
Reference