본문으로 건너뛰기
개발 머꼬
개발 노트React
hohyeon.dev30

입력창에 타이핑이 안 되고 controlled 경고까지 뜬 이유

  • #Common Pitfall
  • #Engineering Note
  • #React

문제 발생

서버에서 받은 값으로 폼을 채웠더니 두 가지 문제가 한 번에 났습니다. 처음에는 아예 타이핑이 되지 않았고, onChange를 붙인 뒤에는 콘솔 경고가 떴습니다.

const [user, setUser] = useState(null);   // 로딩 중에는 null

<input value={user?.name} onChange={...} />
Warning: A component is changing an uncontrolled input to be controlled.

원인 분석

두 개의 다른 문제입니다.

첫 번째는 value만 준 경우입니다. React 문서가 경고 문구를 그대로 보여줍니다 — 폼 필드에 onChange 핸들러 없이 value prop을 주었습니다. 이는 읽기 전용 필드를 렌더합니다. 필드가 변경 가능해야 한다면 defaultValue를 쓰고, 그렇지 않다면 onChangereadOnly를 설정하세요. React가 값을 state에 고정하므로 키 입력이 화면에 반영되지 않습니다.

두 번째가 이번 경고의 원인입니다. 문서는 규칙을 이렇게 정리합니다 — 컴포넌트에 value를 준다면 그 수명 동안 계속 문자열이어야 합니다. 처음에 value={undefined}를 주고 나중에 value="some string"을 줄 수는 없습니다. React가 이 컴포넌트를 비제어로 원했는지 제어로 원했는지 알 수 없기 때문입니다.

usernull인 첫 렌더에서 user?.nameundefined입니다. React는 이를 비제어 입력으로 취급하고, 데이터가 도착해 문자열이 들어오는 순간 제어 입력으로 바뀝니다. 이 전환에서 사용자가 이미 입력하던 값이 덮어써지는 실제 버그도 함께 생깁니다.

해결 방안

  1. value가 항상 문자열이 되게 합니다. 문서가 권하는 방법 그대로입니다.
<input value={user?.name ?? ""} onChange={(e) => setName(e.target.value)} />
  1. 초기값만 지정하고 싶으면 비제어로 갑니다.
<input defaultValue={user?.name} />
  1. 읽기 전용이 의도라면 그렇게 선언합니다. readOnly를 붙이면 경고도 사라지고 의도도 드러납니다.

  2. 데이터 도착 시점 문제를 key로 정리합니다. 로딩이 끝난 뒤 폼을 새로 시작해야 한다면, 조건부로 값을 바꾸는 대신 <Form key={user.id} …/>로 새로 마운트하는 편이 예측 가능합니다.

  3. 체크박스도 같은 규칙입니다. 문서가 함께 적듯 checked를 넘긴다면 항상 boolean이어야 합니다. undefined가 섞이면 동일한 전환이 일어납니다.

  4. 컴포넌트를 만들 때 한쪽을 정합니다. 문서의 핵심 원칙입니다 — 생성 시점에 제어인지 비제어인지 정하고 수명 내내 그 결정을 유지합니다. 재사용 컴포넌트라면 이 계약을 props 타입으로도 드러냅니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.