React 제어와 비제어 입력: 현재 폼 값은 누가 소유하는가
defaultValue와 React 값이 서로 달라지는 닉네임 복원 실패를 따라가며, 입력 중 조정이 필요한 값과 제출할 때만 읽을 값을 구분한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
현재값의 기준은 여러 사본 중 지금 맞다고 판단할 한 위치다. 입력 중 글자 수를 보여 주거나 복원 버튼으로 값을 바꾸는 등 React 화면이 매 편집과 함께 움직여야 하면 React가 현재값을 소유하는 제어 입력을 쓴다. 제출할 때만 값이 필요하면 실제 입력 요소가 현재값을 소유하는 비제어 입력도 충분하다.
이 글을 관통하는 상황: 복원 문구와 입력창이 서로 다른 이름을 보인다
프로필 닉네임 입력에 현재 글자 수와 서버 초안 복원 버튼을 붙였다고 하자. 다음 Web 예제는
defaultValue와 React가 기억하는 값을 함께 현재값처럼 사용한다.
React의 onChange 처리 함수는 입력이 바뀔 때 사건 정보를 받는다. 이 예제에서
event.target.value는 방금 바뀐 입력 요소의 현재 문자열이다. 따라서 사용자가 타이핑할 때마다 그
문자열을 setName에 전달한다.
import { useState } from 'react'
export default function ProfileNameForm() {
const [name, setName] = useState('민수')
return (
<section>
<label>
닉네임
<input
defaultValue={name}
onChange={event => setName(event.target.value)}
/>
</label>
<p>React가 기억한 값: {name}</p>
<p>글자 수: {name.length}</p>
<button type="button" onClick={() => setName('서버 초안')}>
서버 초안 복원
</button>
</section>
)
}실행 전에 예측한다.
- 입력창에
새 이름을 입력하면 화면의 React 값과 글자 수도 따라 바뀐다. 서버 초안 복원을 누르면 React 값과 글자 수는서버 초안기준으로 바뀐다.- 그러나 입력창에는 사용자가 쓴
새 이름이 그대로 남는다.
사용자 입력은 브라우저 요소가 바꾸고 onChange는 그 사본을 React에도 복제했다. 반면 복원 버튼은
React 사본만 바꿨다. 두 위치를 모두 현재값처럼 사용했기 때문에 어느 이름이 맞는지 갈라졌다.
defaultValue는 현재값을 제어하지 않는다
defaultValue={name}이 다음 Render에서 새 문자열을 받더라도 제어 입력의 value처럼 현재 표시값을
강제로 맞추지 않는다. 이 동작은 비제어 입력의 오류가 아니라 입력 요소가 현재값을 소유한다는 계약이다.
문제는 비제어를 선택한 것 자체가 아니다. 현재값으로부터 글자 수를 계산하고 React 버튼으로 복원해야 하면서, 브라우저 값과 React 값을 동시에 기준으로 둔 선택이 문제다.
value와 onChange로 현재값의 기준을 하나로 만든다
같은 사례를 제어 입력으로 교정한다.
import { useState } from 'react'
export default function ProfileNameForm() {
const [name, setName] = useState('민수')
return (
<section>
<label>
닉네임
<input
value={name}
onChange={event => setName(event.target.value)}
/>
</label>
<p>React가 기억한 값: {name}</p>
<p>글자 수: {name.length}</p>
<button type="button" onClick={() => setName('서버 초안')}>
서버 초안 복원
</button>
</section>
)
}사용자 편집은 onChange → setName → 다음 value로 돌아온다. 복원 버튼도 같은 setName을 사용한다.
입력창, 화면 문구와 글자 수가 모두 name 하나를 읽으므로 어느 경로로 바뀌어도 서로 어긋나지 않는다.
value만 넣고 onChange에서 name을 갱신하지 않으면 입력할 때마다 React가 기존 value로 되돌려
편집할 수 없게 된다. 제어란 사용자의 입력을 막는다는 뜻이 아니라, 모든 변경 경로가 같은 현재값을
갱신한다는 뜻이다.
제출할 때만 필요하면 입력 요소가 소유해도 된다
제어 입력이 항상 더 좋은 것은 아니다. 검색어를 입력하는 동안 다른 문구, 검증이나 버튼이 그 값을 읽지 않고 제출할 때만 필요하다면 비제어 입력으로 충분하다.
import { useState } from 'react'
export default function SearchForm() {
const [submittedQuery, setSubmittedQuery] = useState('없음')
function handleSubmit(event) {
event.preventDefault()
const data = new FormData(event.currentTarget)
const query = data.get('query')
setSubmittedQuery(typeof query === 'string' ? query : '')
}
return (
<form onSubmit={handleSubmit}>
<label>
검색어
<input name="query" defaultValue="React" />
</label>
<button type="submit">검색</button>
<output>마지막 검색: {submittedQuery}</output>
</form>
)
}입력 중 검색어는 브라우저 입력 요소가 보관한다. React는 제출할 때 읽은 결과만 기억한다. 이 경계에서는 입력 중 현재값의 React 사본이 없으므로 두 사본이 어긋날 문제도 없다.
다음 요구가 생기면 결정을 다시 본다.
- 입력할 때마다 결과 목록이나 남은 글자 수를 계산해야 한다.
- 외부 복원 버튼이나 다른 입력이 현재값을 바꿔야 한다.
- 여러 컴포넌트가 같은 현재값을 읽고 바꿔야 한다.
이 중 하나가 필요하면 제어 입력으로 옮길 이유가 생긴다. 반대로 “React 폼이므로”라는 이유만으로 모든 키 입력을 state에 복제하지 않는다.
한 input의 소유자를 도중에 바꾸지 않는다
서버 닉네임이 아직 없다고 다음처럼 쓰면 최초에는 비제어였다가 응답 뒤 제어 입력이 된다.
<input value={profile?.name} onChange={handleChange} />처음부터 제어할 입력이라면 빈 문자열을 명시한다.
<input value={profile?.name ?? ''} onChange={handleChange} />이 규칙은 “서버 응답이 늦으면 비제어가 더 낫다”는 뜻이 아니다. 로딩 중 입력을 보여 줄지, 임시 편집을 허용할지 먼저 정하고, 실제로 화면에 유지되는 같은 input의 소유 방식을 안정적으로 유지하라는 뜻이다.
체크박스와 라디오 버튼은 텍스트의 value 대신 불리언 checked로 제어하고, 초기값만 주려면
defaultChecked를 쓴다.
React Native도 소유 질문은 같고 입력 방식은 다르다
닉네임 복원과 글자 수가 필요하다면 Native 구현도 React가 값을 소유하게 할 수 있다.
import { Button, Text, TextInput, View } from 'react-native'
import { useState } from 'react'
export default function ProfileNameForm() {
const [name, setName] = useState('민수')
return (
<View>
<TextInput
accessibilityLabel="닉네임"
value={name}
onChangeText={setName}
/>
<Text>현재 값: {name}</Text>
<Text>글자 수: {name.length}</Text>
<Button title="서버 초안 복원" onPress={() => setName('서버 초안')} />
</View>
)
}Web의 event.target.value와 Native의 onChangeText는 다른 플랫폼 입력 방식이다. 하지만 현재값을 입력 요소에
맡길지 React 값으로 조정할지 묻는 소유권 판단은 같다. 제출 방식과 키보드 동작까지 같다고 일반화하지
않는다.
같은 닉네임 변경을 전후로 검증한다
새 이름을 입력했을 때 입력창, 현재 값과 글자 수가 모두 일치하는지 확인한다.서버 초안 복원을 눌렀을 때 세 표시가 모두서버 초안기준으로 바뀌는지 확인한다.- 빈 문자열까지 지운 뒤에도 입력이 제어 상태를 유지하는지 확인한다.
- Web에서는 입력과 버튼을 키보드로 조작하고 콘솔에 제어 전환 경고가 없는지 확인한다.
- Native에서는 실제 시뮬레이터나 기기에서 입력, 복원과 키보드 조합을 확인한다.
문제 복원 뒤 입력창과 React 문구가 서로 다른 닉네임을 표시함
잘못된 판단 defaultValue와 state를 함께 현재값으로 써도 자동으로 동기화됨
관찰 버튼은 React 값만 바꾸고 편집된 입력창은 이전 문자열을 유지함
원인 브라우저 입력과 React가 같은 현재값의 서로 다른 사본을 소유함
행동 value와 onChange를 연결해 모든 변경 경로를 name 하나로 모음
검증 입력·복원·빈 문자열에서 표시가 일치하고 제어 전환 경고가 없음풀 리퀘스트에서는 다음을 확인한다.
- 입력 중 다른 화면이 현재값을 읽거나 바꿔야 하는가?
defaultValue를 현재값 갱신 수단으로 오해하지 않았는가?- 제어 입력의
value와 변경 처리가 같은 값을 바로 갱신하는가? - 브라우저 값과 React 값 두 개를 모두 현재 기준으로 두지는 않았는가?
- 제출할 때만 필요한 값을 불필요하게 매 키 입력마다 React에 복제하지 않았는가?
- 같은 input이 수명 중 제어와 비제어 사이를 바꾸지 않는가?
- Web과 Native의 입력 사건·제출 차이를 별도로 검증했는가?
기억할 질문은 하나다.
이 입력의 현재값을 지금 누가 소유해야 하는가?
다음 글에서는 클라이언트가 원본인 화면 상태와 서버가 원본인 데이터 사본을 구분한다.
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01defaultValue로 시작한 닉네임 입력에서 React 값만 서버 초안으로 바꾸면 화면 입력이 바뀌지 않는 이유는 무엇인가?
정답
defaultValue는 비제어 입력의 초기값만 정하고, 편집 뒤 현재값은 브라우저 입력 요소가 소유하기 때문이다.
관련 설명 다시 읽기왜 그런가
React가 기억한 name과 브라우저가 표시하는 값이 서로 다른 소유자에게 있어 복원 버튼 뒤 불일치가 드러난다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02Web 텍스트 입력을 제어하려면 value와 onChange가 어떤 한 흐름을 만들어야 하는가?
정답
value로 React가 기억한 현재 문자열을 표시하고, onChange에서 사용자가 입력한 문자열로 그 값을 바로 갱신해야 한다.
관련 설명 다시 읽기왜 그런가
표시값과 글자 수, 복원 버튼이 모두 같은 React 값을 읽고 바꾸므로 현재값의 기준이 하나가 된다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03검색어를 입력 중 다른 화면과 맞출 필요가 없고 제출할 때만 필요하다면 어떤 소유 방식을 선택할 수 있는가?
정답
입력 요소가 현재값을 보관하는 비제어 입력을 두고, 제출 시 FormData로 현재값을 읽을 수 있다.
관련 설명 다시 읽기왜 그런가
중간값으로 다른 UI를 계산하거나 외부에서 값을 바꾸지 않으므로 React가 매 입력을 소유할 이유가 없다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04서버 값이 처음에는 undefined이고 나중에 문자열이 된다면 같은 input에 value를 그대로 전달해도 되는가?
정답
안 된다. 제어 입력을 선택했다면 처음부터 빈 문자열 등 문자열 value를 전달해 수명 동안 제어 상태를 유지해야 한다.
관련 설명 다시 읽기왜 그런가
React DOM input은 비제어와 제어 사이를 수명 중 전환할 수 없으므로 값의 타입과 소유 방식을 처음부터 안정적으로 정한다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- inputReact · 공식 문서 · 확인 2026-08-09
- Sharing State Between ComponentsReact · 공식 문서 · 확인 2026-08-09
- React event objectReact · 공식 문서 · 확인 2026-08-09
- FormData constructorMDN Web Docs · 공식 문서 · 확인 2026-08-09
- TextInputReact Native · 공식 문서 · 확인 2026-08-09