<input>

김동현·2026년 3월 17일

<input>

소개

브라우저 내장 <input> 컴포넌트를 사용하면 다양한 종류의 폼 입력을 렌더링할 수 있어요.

<input />

input은 사용자로부터 텍스트, 숫자, 체크박스, 라디오 버튼 등 다양한 형태의 데이터를 입력받을 수 있는 기본적이고 중요한 폼 요소예요!


목차


레퍼런스

<input>

input을 표시하려면, 브라우저 내장 <input> 컴포넌트를 렌더링하세요.

<input name="myInput" />

아래에서 더 많은 예시를 확인해보세요.

Props

<input>은 모든 공통 요소 props를 지원해요.

  • formAction: 문자열 또는 함수예요. type="submit"type="image"에 대해 부모 <form action>을 재정의해요. URL이 action에 전달되면 폼은 표준 HTML 폼처럼 동작해요. 함수가 formAction에 전달되면 그 함수가 폼 제출을 처리할 거예요. <form action>을 참고하세요.

다음 props 중 하나를 전달해서 input을 제어할 수 있어요:

  • checked: boolean이에요. 체크박스 input이나 라디오 버튼이 선택되었는지를 제어해요.
  • value: 문자열이에요. 텍스트 input의 텍스트를 제어해요. (라디오 버튼의 경우, 폼 데이터를 지정해요.)

이들 중 하나를 전달하면, 전달된 값을 업데이트하는 onChange 핸들러도 반드시 전달해야 해요.

다음 <input> props는 비제어 input에만 관련이 있어요:

다음 <input> props는 비제어 input과 제어 input 모두에 관련이 있어요:

  • accept: 문자열이에요. type="file" input이 허용하는 파일 유형을 지정해요.
  • alt: 문자열이에요. type="image" input의 대체 이미지 텍스트를 지정해요.
  • capture: 문자열이에요. type="file" input이 캡처하는 미디어(마이크, 비디오, 카메라)를 지정해요.
  • autoComplete: 문자열이에요. 가능한 자동완성 동작 중 하나를 지정해요.
  • autoFocus: boolean이에요. true면, React가 마운트 시 요소에 포커스할 거예요.
  • dirname: 문자열이에요. 요소의 방향성(directionality)에 대한 폼 필드 이름을 지정해요.
  • disabled: boolean이에요. true면, input은 인터랙티브하지 않고 흐리게 표시될 거예요.
  • children: <input>은 children을 받지 않아요.
  • form: 문자열이에요. 이 input이 속한 <form>id를 지정해요. 생략되면, 가장 가까운 부모 form이에요.
  • formAction: 문자열이에요. type="submit"type="image"에 대해 부모 <form action>을 재정의해요.
  • formEnctype: 문자열이에요. type="submit"type="image"에 대해 부모 <form enctype>을 재정의해요.
  • formMethod: 문자열이에요. type="submit"type="image"에 대해 부모 <form method>를 재정의해요.
  • formNoValidate: 문자열이에요. type="submit"type="image"에 대해 부모 <form noValidate>를 재정의해요.
  • formTarget: 문자열이에요. type="submit"type="image"에 대해 부모 <form target>을 재정의해요.
  • height: 문자열이에요. type="image"의 이미지 높이를 지정해요.
  • list: 문자열이에요. 자동완성 옵션이 있는 <datalist>id를 지정해요.
  • max: 숫자예요. 숫자 및 날짜/시간 input의 최대값을 지정해요.
  • maxLength: 숫자예요. 텍스트 및 기타 input의 최대 길이를 지정해요.
  • min: 숫자예요. 숫자 및 날짜/시간 input의 최소값을 지정해요.
  • minLength: 숫자예요. 텍스트 및 기타 input의 최소 길이를 지정해요.
  • multiple: boolean이에요. type="file"type="email"에 대해 여러 값이 허용되는지를 지정해요.
  • name: 문자열이에요. 폼과 함께 제출되는 이 input의 이름을 지정해요.
  • onChange: 이벤트 핸들러 함수예요. 제어 input에 필수예요. 사용자가 input의 값을 변경하면 즉시 실행돼요(예: 키 입력마다 실행됨). 브라우저 input 이벤트처럼 동작해요.
  • onChangeCapture: 캡처 단계에서 실행되는 onChange 버전이에요.
  • onInput: 이벤트 핸들러 함수예요. 사용자가 값을 변경하면 즉시 실행돼요. 역사적인 이유로, React에서는 비슷하게 동작하는 onChange를 사용하는 것이 관용적이에요.
  • onInputCapture: 캡처 단계에서 실행되는 onInput 버전이에요.
  • onInvalid: 이벤트 핸들러 함수예요. 폼 제출 시 input이 유효성 검사에 실패하면 실행돼요. 내장 invalid 이벤트와 달리, React onInvalid 이벤트는 버블링돼요.
  • onInvalidCapture: 캡처 단계에서 실행되는 onInvalid 버전이에요.
  • onSelect: 이벤트 핸들러 함수예요. <input> 내부의 선택 영역이 변경된 후 실행돼요. React는 onSelect 이벤트를 확장해서 빈 선택이나 편집(선택 영역에 영향을 줄 수 있음)에 대해서도 실행돼요.
  • onSelectCapture: 캡처 단계에서 실행되는 onSelect 버전이에요.
  • pattern: 문자열이에요. value가 일치해야 하는 패턴을 지정해요.
  • placeholder: 문자열이에요. input 값이 비어있을 때 흐린 색으로 표시돼요.
  • readOnly: boolean이에요. true면, 사용자가 input을 편집할 수 없어요.
  • required: boolean이에요. true면, 폼을 제출하려면 값이 제공되어야 해요.
  • size: 숫자예요. 너비 설정과 비슷하지만, 단위는 컨트롤에 따라 달라요.
  • src: 문자열이에요. type="image" input의 이미지 소스를 지정해요.
  • step: 양수 또는 'any' 문자열이에요. 유효한 값 사이의 간격을 지정해요.
  • type: 문자열이에요. input 유형 중 하나예요.
  • width: 문자열이에요. type="image" input의 이미지 너비를 지정해요.

주의사항

  • 체크박스는 value(또는 defaultValue)가 아니라 checked(또는 defaultChecked)가 필요해요.
  • 텍스트 input이 문자열 value prop을 받으면, 제어되는 것으로 취급될 거예요.
  • 체크박스나 라디오 버튼이 boolean checked prop을 받으면, 제어되는 것으로 취급될 거예요.
  • input은 동시에 제어되면서 비제어일 수 없어요.
  • input은 생명주기 동안 제어와 비제어 사이를 전환할 수 없어요.
  • 모든 제어 input은 뒷받침하는 값을 동기적으로 업데이트하는 onChange 이벤트 핸들러가 필요해요.

사용법

다양한 유형의 input 표시하기

input을 표시하려면, <input> 컴포넌트를 렌더링하세요. 기본적으로 텍스트 input이 될 거예요. 체크박스는 type="checkbox", 라디오 버튼은 type="radio", 또는 다른 input 유형 중 하나를 전달할 수 있어요.

export default function MyForm() {
  return (
    <>
      <label>
        Text input: <input name="myInput" />
      </label>
      <hr />
      <label>
        Checkbox: <input type="checkbox" name="myCheckbox" />
      </label>
      <hr />
      <p>
        Radio buttons:
        <label>
          <input type="radio" name="myRadio" value="option1" />
          Option 1
        </label>
        <label>
          <input type="radio" name="myRadio" value="option2" />
          Option 2
        </label>
        <label>
          <input type="radio" name="myRadio" value="option3" />
          Option 3
        </label>
      </p>
    </>
  );
}
label { display: block; }
input { margin: 5px; }

input에 라벨 제공하기

일반적으로 모든 <input><label> 태그 안에 배치할 거예요. 이렇게 하면 브라우저에게 이 라벨이 해당 input과 연결되어 있다고 알려줘요. 사용자가 라벨을 클릭하면, 브라우저가 자동으로 input에 포커스할 거예요. 접근성에도 필수적이에요: 스크린 리더가 사용자가 연결된 input에 포커스할 때 라벨 캡션을 알려줄 거예요.

<input><label> 안에 중첩할 수 없다면, <input id><label htmlFor>에 같은 ID를 전달해서 연결하세요. 한 컴포넌트의 여러 인스턴스 간 충돌을 피하려면, useId로 그런 ID를 생성하세요.

import { useId } from 'react';

export default function Form() {
  const ageInputId = useId();
  return (
    <>
      <label>
        Your first name:
        <input name="firstName" />
      </label>
      <hr />
      <label htmlFor={ageInputId}>Your age:</label>
      <input id={ageInputId} name="age" type="number" />
    </>
  );
}
input { margin: 5px; }

input의 초기 값 제공하기

모든 input의 초기 값을 선택적으로 지정할 수 있어요. 텍스트 input은 defaultValue 문자열로 전달하세요. 체크박스와 라디오 버튼은 대신 defaultChecked boolean으로 초기 값을 지정해야 해요.

export default function MyForm() {
  return (
    <>
      <label>
        Text input: <input name="myInput" defaultValue="Some initial value" />
      </label>
      <hr />
      <label>
        Checkbox: <input type="checkbox" name="myCheckbox" defaultChecked={true} />
      </label>
      <hr />
      <p>
        Radio buttons:
        <label>
          <input type="radio" name="myRadio" value="option1" />
          Option 1
        </label>
        <label>
          <input
            type="radio"
            name="myRadio"
            value="option2"
            defaultChecked={true} 
          />
          Option 2
        </label>
        <label>
          <input type="radio" name="myRadio" value="option3" />
          Option 3
        </label>
      </p>
    </>
  );
}
label { display: block; }
input { margin: 5px; }

폼 제출 시 input 값 읽기

input 주위에 <form>을 추가하고 내부에 <button type="submit">을 넣으세요. <form onSubmit> 이벤트 핸들러를 호출할 거예요. 기본적으로 브라우저는 폼 데이터를 현재 URL로 전송하고 페이지를 새로고침해요. e.preventDefault()를 호출해서 그 동작을 재정의할 수 있어요. new FormData(e.target)로 폼 데이터를 읽으세요.

export default function MyForm() {
  function handleSubmit(e) {
    // 브라우저가 페이지를 새로고침하는 것을 막아요
    e.preventDefault();

    // 폼 데이터를 읽어요
    const form = e.target;
    const formData = new FormData(form);

    // formData를 fetch body로 직접 전달할 수 있어요:
    fetch('/some-api', { method: form.method, body: formData });

    // 또는 일반 객체로 작업할 수 있어요:
    const formJson = Object.fromEntries(formData.entries());
    console.log(formJson);
  }

  return (
    <form method="post" onSubmit={handleSubmit}>
      <label>
        Text input: <input name="myInput" defaultValue="Some initial value" />
      </label>
      <hr />
      <label>
        Checkbox: <input type="checkbox" name="myCheckbox" defaultChecked={true} />
      </label>
      <hr />
      <p>
        Radio buttons:
        <label><input type="radio" name="myRadio" value="option1" /> Option 1</label>
        <label><input type="radio" name="myRadio" value="option2" defaultChecked={true} /> Option 2</label>
        <label><input type="radio" name="myRadio" value="option3" /> Option 3</label>
      </p>
      <hr />
      <button type="reset">Reset form</button>
      <button type="submit">Submit form</button>
    </form>
  );
}
label { display: block; }
input { margin: 5px; }

참고

모든 <input>name을 지정하세요. 예를 들어 <input name="firstName" defaultValue="Taylor" />. 지정한 name은 폼 데이터에서 키로 사용될 거예요. 예를 들어 { firstName: "Taylor" }.

⚠️ 주의

기본적으로, type 속성이 없는 <form> 내부의 <button>은 폼을 제출할 거예요. 이건 놀랄 수 있어요! 자체 커스텀 Button React 컴포넌트가 있다면, <button> (type 없이) 대신 <button type="button">을 사용하는 것을 고려하세요. 그런 다음, 명시적으로 하기 위해, 폼을 제출해야 하는 버튼에는 <button type="submit">을 사용하세요.


state 변수로 input 제어하기

<input /> 같은 input은 비제어예요. 초기 값을 전달하더라도 <input defaultValue="Initial text" />처럼, JSX는 초기 값만 지정해요. 지금 값이 무엇이어야 하는지는 제어하지 않아요.

제어 input을 렌더링하려면, value prop을 전달하세요(체크박스와 라디오는 checked). React는 input이 항상 전달한 value를 갖도록 강제할 거예요. 일반적으로 state 변수를 선언해서 이렇게 해요:

function Form() {
  const [firstName, setFirstName] = useState(''); // state 변수를 선언해요...
  // ...
  return (
    <input
      value={firstName} // ...input의 값이 state 변수와 일치하도록 강제해요...
      onChange={e => setFirstName(e.target.value)} // ... 그리고 모든 편집마다 state 변수를 업데이트해요!
    />
  );
}

제어 input은 어쨌든 state가 필요했다면 의미가 있어요--예를 들어, 매 편집마다 UI를 다시 렌더링하려면:

function Form() {
  const [firstName, setFirstName] = useState('');
  return (
    <>
      <label>
        First name:
        <input value={firstName} onChange={e => setFirstName(e.target.value)} />
      </label>
      {firstName !== '' && <p>Your name is {firstName}.</p>}
      ...

input state를 조정하는 여러 방법을 제공하고 싶다면(예: 버튼 클릭으로) 유용해요:

function Form() {
  // ...
  const [age, setAge] = useState('');
  const ageAsNumber = Number(age);
  return (
    <>
      <label>
        Age:
        <input
          value={age}
          onChange={e => setAge(e.target.value)}
          type="number"
        />
        <button onClick={() => setAge(ageAsNumber + 10)}>
          Add 10 years
        </button>

제어 컴포넌트에 전달하는 valueundefinednull이 아니어야 해요. 초기 값이 비어있어야 한다면(아래 firstName 필드처럼), state 변수를 빈 문자열('')로 초기화하세요.

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [age, setAge] = useState('20');
  const ageAsNumber = Number(age);
  return (
    <>
      <label>
        First name:
        <input
          value={firstName}
          onChange={e => setFirstName(e.target.value)}
        />
      </label>
      <label>
        Age:
        <input
          value={age}
          onChange={e => setAge(e.target.value)}
          type="number"
        />
        <button onClick={() => setAge(ageAsNumber + 10)}>
          Add 10 years
        </button>
      </label>
      {firstName !== '' &&
        <p>Your name is {firstName}.</p>
      }
      {ageAsNumber > 0 &&
        <p>Your age is {ageAsNumber}.</p>
      }
    </>
  );
}
label { display: block; }
input { margin: 5px; }
p { font-weight: bold; }

⚠️ 주의

onChange 없이 value를 전달하면, input에 타이핑하는 것이 불가능할 거예요. value를 전달해서 input을 제어하면, 전달한 값을 항상 갖도록 강제해요. 그래서 state 변수를 value로 전달하지만 onChange 이벤트 핸들러 동안 그 state 변수를 동기적으로 업데이트하는 것을 잊으면, React는 모든 키 입력 후 input을 지정한 value로 되돌릴 거예요.


키 입력마다 리렌더링 최적화하기

제어 input을 사용하면, 모든 키 입력마다 state를 설정해요. state를 포함하는 컴포넌트가 큰 트리를 다시 렌더링하면, 느려질 수 있어요. 리렌더링 성능을 최적화하는 몇 가지 방법이 있어요.

예를 들어, 모든 키 입력마다 모든 페이지 콘텐츠를 다시 렌더링하는 폼으로 시작한다고 가정해보세요:

function App() {
  const [firstName, setFirstName] = useState('');
  return (
    <>
      <form>
        <input value={firstName} onChange={e => setFirstName(e.target.value)} />
      </form>
      <PageContent />
    </>
  );
}

<PageContent />가 input state에 의존하지 않으니까, input state를 자체 컴포넌트로 이동할 수 있어요:

function App() {
  return (
    <>
      <SignupForm />
      <PageContent />
    </>
  );
}

function SignupForm() {
  const [firstName, setFirstName] = useState('');
  return (
    <form>
      <input value={firstName} onChange={e => setFirstName(e.target.value)} />
    </form>
  );
}

이렇게 하면 성능이 크게 향상돼요. 이제 모든 키 입력마다 SignupForm만 다시 렌더링되니까요.

리렌더링을 피할 방법이 없다면(예: PageContent가 검색 input의 값에 의존한다면), useDeferredValue를 사용하면 큰 리렌더링 중간에도 제어 input을 반응적으로 유지할 수 있어요.


문제 해결

텍스트 input에 타이핑해도 업데이트되지 않아요

value는 있지만 onChange가 없는 input을 렌더링하면, 콘솔에 에러가 표시될 거예요:

// 🔴 버그: onChange 핸들러가 없는 제어 텍스트 input
<input value={something} />

onChange 핸들러 없이 폼 필드에 value prop을 제공했어요. 이렇게 하면 읽기 전용 필드가 렌더링될 거예요. 필드가 변경 가능해야 한다면 defaultValue를 사용하세요. 그렇지 않으면, onChangereadOnly 중 하나를 설정하세요.

에러 메시지가 제안하듯이, 초기 값만 지정하고 싶었다면, 대신 defaultValue를 전달하세요:

// ✅ 좋아요: 초기 값이 있는 비제어 input
<input defaultValue={something} />

state 변수로 이 input을 제어하고 싶다면, onChange 핸들러를 지정하세요:

// ✅ 좋아요: onChange가 있는 제어 input
<input value={something} onChange={e => setSomething(e.target.value)} />

값이 의도적으로 읽기 전용이라면, readOnly prop을 추가해서 에러를 제거하세요:

// ✅ 좋아요: onChange 없는 읽기 전용 제어 input
<input value={something} readOnly={true} />

체크박스를 클릭해도 업데이트되지 않아요

checked는 있지만 onChange가 없는 체크박스를 렌더링하면, 콘솔에 에러가 표시될 거예요:

// 🔴 버그: onChange 핸들러가 없는 제어 체크박스
<input type="checkbox" checked={something} />

onChange 핸들러 없이 폼 필드에 checked prop을 제공했어요. 이렇게 하면 읽기 전용 필드가 렌더링될 거예요. 필드가 변경 가능해야 한다면 defaultChecked를 사용하세요. 그렇지 않으면, onChangereadOnly 중 하나를 설정하세요.

에러 메시지가 제안하듯이, 초기 값만 지정하고 싶었다면, 대신 defaultChecked를 전달하세요:

// ✅ 좋아요: 초기 값이 있는 비제어 체크박스
<input type="checkbox" defaultChecked={something} />

state 변수로 이 체크박스를 제어하고 싶다면, onChange 핸들러를 지정하세요:

// ✅ 좋아요: onChange가 있는 제어 체크박스
<input type="checkbox" checked={something} onChange={e => setSomething(e.target.checked)} />

⚠️ 주의

체크박스는 e.target.value가 아니라 e.target.checked를 읽어야 해요.

체크박스가 의도적으로 읽기 전용이라면, readOnly prop을 추가해서 에러를 제거하세요:

// ✅ 좋아요: onChange 없는 읽기 전용 제어 input
<input type="checkbox" checked={something} readOnly={true} />

키 입력마다 input 캐럿이 시작 부분으로 점프해요

input을 제어한다면, onChange 동안 state 변수를 DOM의 input 값으로 업데이트해야 해요.

e.target.value(체크박스는 e.target.checked) 이외의 것으로 업데이트할 수 없어요:

function handleChange(e) {
  // 🔴 버그: e.target.value 이외의 것으로 input 업데이트
  setFirstName(e.target.value.toUpperCase());
}

비동기적으로도 업데이트할 수 없어요:

function handleChange(e) {
  // 🔴 버그: 비동기적으로 input 업데이트
  setTimeout(() => {
    setFirstName(e.target.value);
  }, 100);
}

코드를 수정하려면, e.target.value로 동기적으로 업데이트하세요:

function handleChange(e) {
  // ✅ e.target.value로 제어 input을 동기적으로 업데이트
  setFirstName(e.target.value);
}

이렇게 해도 문제가 해결되지 않으면, 모든 키 입력마다 input이 DOM에서 제거되고 다시 추가되는 것일 수 있어요. 이건 모든 리렌더링마다 실수로 state를 리셋하는 경우 발생할 수 있어요. 예를 들어 input이나 부모 중 하나가 항상 다른 key 속성을 받거나, 컴포넌트 함수 정의를 중첩하는 경우(지원되지 않고 "내부" 컴포넌트가 항상 다른 트리로 간주됨) 그럴 수 있어요.


"컴포넌트가 비제어 input을 제어되도록 변경하고 있습니다" 에러가 발생해요

컴포넌트에 value를 제공하면, 생명주기 동안 문자열로 유지되어야 해요.

먼저 value={undefined}를 전달하고 나중에 value="some string"을 전달할 수 없어요. React는 컴포넌트를 비제어로 할지 제어로 할지 알 수 없어요. 제어 컴포넌트는 항상 문자열 value를 받아야 해요. null이나 undefined가 아니에요.

value가 API나 state 변수에서 오는 경우, null이나 undefined로 초기화될 수 있어요. 그런 경우, 처음에 빈 문자열('')로 설정하거나, value={someValue ?? ''}를 전달해서 value가 문자열이 되도록 하세요.

마찬가지로, 체크박스에 checked를 전달한다면, 항상 boolean이어야 해요.


사이트맵

모든 문서 페이지 개요

profile
프론트에_가까운_풀스택_개발자

0개의 댓글