React에서 데이터 바인딩(Data Binding)은 입력 필드의 값과 React 상태(state)를 연결하는 걸 뜻합니다. React에서 입력값을 다루려면 이벤트 핸들러를 통해 명시적으로 바인딩 된 상태를 업데이트해야 합니다. 이번 편에서는 폼의 입력 필드와 컴포넌트 상태를 연결하고 이벤트를 처리하는 방법을 알아보겠습니다.
먼저, 폼에서 흔히 사용되는 이벤트 몇 가지를 알아보겠습니다.
- onChange : 입력 필드의 값이 변경될 때 호출됩니다. 텍스트 입력, 체크박스 토글 등 대부분의 폼 요소에서 지원하는 이벤트입니다.
- onSubmit : 폼이 제출될 때 호출됩니다. <form> 태그에 지정하며, 주로 폼 전체 데이터를 취합하여 처리할 때 사용합니다.
- onClick : 버튼 클릭 등에 사용되지만, 폼 컨텍스트에서는 주로 <button type="submit">을 눌렀을 때 onSubmit과 함께 사용됩니다. 일반적으로 onClick보다는 onSubmit에서 폼 처리를 합니다.
- onFocus : 입력 필드에 포커스가 들어왔을 때 호출됩니다.
- onBlur : 입력 필드가 포커스를 잃을 때 호출됩니다. 유효성 검사를 필드 단위로 제어할 때 유용합니다. 예를 들어, 사용자가 입력을 마치고 탭 키로 포커스가 이동했을 때 유효성을 검사하는 거죠.
이 중 onChange와 onSubmit이 폼 처리에서 주요하게 사용됩니다. React에서는 입력 필드의 onChange 이벤트를 이용해 사용자의 입력을 실시간으로 상태에 반영하고, onSubmit 이벤트를 이용해 폼 제출 시 필요한 동작을 수행합니다.
앞서 작성한 SimpleForm 코드를 다시 살펴보면, input 태그에 value={name}과 onChange={(e) => setName(e.target.value)}가 들어있습니다. 이것이 데이터 바인딩의 한 형태인데, value 속성은 이 input의 표시값을 React 상태 변수 name으로 설정하고, onChange 핸들러는 입력 값이 변할 때마다 호출되어 name 상태를 업데이트합니다.
이렇게 하면 상태와 입력 필드 값이 동기화되며, React 상태인 name이 변경될 때마다 컴포넌트가 리렌더링되고, 입력 필드에도 새로운 값이 반영됩니다. 반대로 사용자가 입력 필드 내용을 수정하면 onChange를 통해 상태가 변경되죠. 이처럼 사용자 입력과 상태 간의 흐름이 계속 이어지는 것이 React에서의 데이터 바인딩입니다.
이 과정을 그림으로 정리하면 다음과 같습니다.

- 초기 렌더링 시 useState로 name을 "" (빈 문자열)로 초기화.
- input의 value에 name을 지정했으므로, 처음엔 input이 빈 상태로 표시됨.
- 사용자가 키보드로 문자를 입력 -> onChange 이벤트 발생 -> 이벤트 핸들러에서 setName(e.target.value) 호출.
- 상태 name이 새로운 값으로 업데이트되고 컴포넌트 재렌더링.
- 재렌더링된 컴포넌트에서 input value={name}이므로, input 표시값이 최신 name으로 업데이트.
- 사용자가 더 입력하거나 삭제 -> 3~5 반복
- 사용자가 "제출" 버튼 클릭 -> onSubmit 이벤트 (handleSubmit 함수) 호출 -> e.preventDefault()로 페이지 새로고침 막음 -> alert 표시 (또는 데이터 처리 로직 수행).
여기서 유의깊게 봐야할 부분은 React의 state가 단일 진실의 원천(single source of truth)이 된다는 점입니다. 즉, input 필드에 표시되는 값은 항상 React state에서 가져오며, 사용자가 입력할 때마다 React state를 변경해서 둘 사이를 일치시킵니다. 이러한 방식을 제어 컴포넌트(Controlled Component)라고 부릅니다. 자세한 설명은 다음 다음 편에서 다룹니다.
이제 위의 예시를 조금 확장해서 여러 개의 입력 필드를 가진 폼과 이벤트 핸들링을 살펴보겠습니다. 이름과 이메일을 함께 입력받는 폼으로 발전시켜 볼까요?
// SignUpFormStep1.jsx
import { useState } from 'react';
function SignUpFormStep1() {
const [name, setName] = useState(""); // 이름 입력값 상태
const [email, setEmail] = useState(""); // 이메일 입력값 상태
const handleNameChange = (e) => {
setName(e.target.value); // 이름 상태 업데이트
};
const handleEmailChange = (e) => {
setEmail(e.target.value); // 이메일 상태 업데이트
};
const handleSubmit = (e) => {
e.preventDefault(); // 기본 폼 제출 동작 막기 (페이지 리로드 방지)
console.log("제출됨:", { name, email });
};
return (
);
}
export default SignUpFormStep1;
이 코드에서 이름과 이메일 두 가지 입력을 받습니다. 각각의 필드에 대해 별도의 상태(name, email)와 onChange 핸들러(handleNameChange, handleEmailChange)를 만들었습니다. onSubmit 핸들러인 handleSubmit에서는 현재 상태값인 name과 email을 콘솔에 출력하고 있습니다. 실제로는 이 부분에서 백엔드 API를 호출하거나 상위 컴포넌트에 데이터를 전달하는 로직이 들어가겠지요.
지금 코드에서는 이메일 입력에 type="email"을 사용했습니다. 이렇게 하면 브라우저 단에서 간단한 유효성 검사가 동작합니다. 예를 들어, 이메일 형식에 맞지 않는 값을 넣고 제출하면, 브라우저가 자체적으로 "유효한 이메일 주소를 입력하세요" 같은 경고를 띄울 수 있습니다. 그러나 React에서 onSubmit을 가로채서 기본 동작을 막아서 브라우저 기본 검사보다는 애플리케이션 차원의 검증 로직을 구현하는 경우가 많습니다.
이벤트 핸들링 시 주의할 점
e.preventDefault()는 폼 제출 시 반드시 호출해주는 것이 좋습니다. 잊어버리면 폼이 제출되면서 페이지가 새로고침되어 React 애플리케이션 상태가 초기화됩니다. 사용자가 입력한 데이터나 현재 앱의 상태를 모두 잃는 거지요. 입문자가 흔히 하는 실수입니다.
여러 입력 필드를 관리할 때, 각각의 필드에 대해 개별 상태와 핸들러를 만드는 방법을 썼습니다. 이 방법은 필드 수가 적을 때는 문제가 없지만, 필드가 많아지면 비슷한 코드가 반복됩니다. 예를 들어, 이름, 이메일 외에 비밀번호, 주소, 전화번호 등 5~6개의 필드가 있으면 상태 변수와 onChange 핸들러도 5~6개가 생기겠죠. 이는 중복 코드를 늘리고 실수를 유발합니다. 다음 편에서 이러한 경우, 상태를 구조적으로 관리하는 방법을 알아보겠습니다.
위 코드에서는 <input name="email" ...> 처럼 name을 지정했습니다. 이 속성은 주로 브라우저의 폼 제출 혹은 FormData 생성 등에 사용되지만, React에서도 유용하게 쓸 수 있습니다. 예를 들어 다수의 필드를 하나의 이벤트 핸들러로 처리할 때 e.target.name을 활용하면 어떤 필드에서 변경 이벤트가 발생했는지 식별할 수 있습니다.
다음 편에서는 폼에서 폼 항목이 늘어나는 상황에서 상태를 좀 더 효율적으로 관리하는 방법과 React 훅을 활용한 폼 상태 관리 기법을 알아보겠습니다.