전체 목록
ReactMedium#103

React에서 Controlled Component와 Uncontrolled Component의 차이를 설명해주세요.

#React##상태관리#input

답변 포인트

입력값의 원천이 React state인지 DOM 자체인지 생각해보세요.

정답 및 해설

빠른 요약

Controlled Component는 input의 value를 React state로 관리하고 onChange로 동기화합니다. 실시간 검증, 조건부 UI, 폼 상태 제어에 유리합니다.

폼 입력을 다룰 때 React가 값을 직접 관리하면 Controlled Component, DOM 자체가 값을 관리하고 필요할 때만 읽어오면 Uncontrolled Component라고 합니다.

Controlled Component

입력값을 React state에 저장하고, valueonChange로 상태를 동기화합니다.

TSX
function LoginForm() {
  const [email, setEmail] = useState("");

  return (
    <input
      value={email}
      onChange={(event) => setEmail(event.target.value)}
    />
  );
}

입력값의 원천이 React state이므로 검증, 포맷팅, 조건부 UI 처리에 유리합니다.

TSX
function SignupForm() {
  const [password, setPassword] = useState("");
  const isValid = password.length >= 8;

  return (
    <>
      <input
        type="password"
        value={password}
        onChange={(event) => setPassword(event.target.value)}
      />
      <button disabled={!isValid}></button>
    </>
  );
}

Uncontrolled Component

입력값을 React state에 저장하지 않고, ref를 통해 DOM에서 직접 읽습니다.

TSX
function SearchForm() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleSubmit = () => {
    const keyword = inputRef.current?.value ?? "";
    console.log(keyword);
  };

  return (
    <>
      <input ref={inputRef} defaultValue="react" />
      <button onClick={handleSubmit}></button>
    </>
  );
}

초기값은 value가 아니라 defaultValue로 지정합니다.

언제 무엇을 쓰나요?

Controlled Component가 적합한 경우:

  • 입력 중 실시간 검증이 필요할 때
  • 입력값에 따라 버튼 활성화, 에러 메시지, 미리보기 등이 바뀔 때
  • 폼 상태를 상위 컴포넌트나 전역 상태와 동기화해야 할 때
  • 입력값을 강제로 포맷팅해야 할 때

Uncontrolled Component가 적합한 경우:

  • 간단한 폼에서 제출 시점에만 값이 필요할 때
  • 파일 입력처럼 브라우저가 직접 관리해야 하는 값일 때
  • 외부 DOM 라이브러리와 통합할 때

파일 입력은 보통 uncontrolled

TSX
function FileUploader() {
  const fileRef = useRef<HTMLInputElement>(null);

  const upload = () => {
    const file = fileRef.current?.files?.[0];
    if (!file) return;
    console.log(file.name);
  };

  return (
    <>
      <input ref={fileRef} type="file" />
      <button onClick={upload}></button>
    </>
  );
}

주의사항

하나의 input에서 valuedefaultValue를 섞거나, controlled와 uncontrolled를 렌더링 중간에 바꾸면 React 경고가 발생할 수 있습니다.

TSX
// 좋지 않은 예: undefined였다가 문자열로 바뀌면 모드가 전환될 수 있음
<input value={maybeUndefined} onChange={handleChange} />

// 안전한 예
<input value={maybeUndefined ?? ""} onChange={handleChange} />

정리

구분ControlledUncontrolled
값 관리React stateDOM
값 읽기state에서 즉시 접근ref로 필요 시 접근
장점검증/동기화/조건부 UI에 강함구현이 단순하고 리렌더링 부담 적음
대표 사용복잡한 폼파일 입력, 단순 검색

판단 기준과 예시

제어 컴포넌트는 폼 값을 React state가 소유하고, 비제어 컴포넌트는 DOM이 값을 소유합니다. 차이는 “값의 원천(source of truth)이 어디인가”입니다.

TSX
function ControlledInput() {
  const [email, setEmail] = useState('');
  return <input value={email} onChange={(e) => setEmail(e.target.value)} />;
}

function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  return <input ref={inputRef} defaultValue="" />;
}

어떤 것을 선택할까?

  • 실시간 유효성 검사, 입력값 포맷팅, 버튼 활성화 제어가 필요하면 제어 컴포넌트가 적합합니다.
  • 파일 입력, 간단한 검색창, 외부 폼 라이브러리와의 통합에는 비제어 방식이 더 단순할 수 있습니다.
  • 대형 폼은 모든 입력을 state로 올리면 렌더링 비용이 커질 수 있어 React Hook Form 같은 라이브러리가 비제어 방식을 활용합니다.

흔한 실수

TSX
// ❌ value만 있고 onChange가 없으면 읽기 전용처럼 동작
<input value={name} />

// ✅ 수정 가능하게 만들기
<input value={name} onChange={(e) => setName(e.target.value)} />

valuedefaultValue를 섞으면 React 경고가 발생할 수 있습니다. 한 컴포넌트 생명주기 안에서는 제어/비제어 방식을 일관되게 유지하는 것이 좋습니다.

관련 질문

같은 카테고리/태그 기준