입력값을 state로 다루는 제어 컴포넌트와 폼 처리.
폼과 제어 컴포넌트
회원가입, 검색, 설정 화면까지 — 웹은 폼으로 가득합니다. React에서는 입력값을 state와 묶어 다루는 제어 컴포넌트 방식이 기본입니다.
학습 목표
- 제어 컴포넌트가 무엇이고 왜 쓰는지 안다.
- 여러 입력을 하나의 핸들러로 처리할 수 있다.
- 체크박스·셀렉트 등 다양한 입력을 다룰 수 있다.
- 폼 제출을 가로채 원하는 동작을 할 수 있다.
- 비제어 컴포넌트와의 차이를 안다.
제어 컴포넌트
입력 요소의 value 를 state로 묶고, onChange 로 state를 갱신합니다. 화면에 보이는 값과 state가 항상 일치하게 됩니다.
import { useState } from 'react';
function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>입력값: {name}</p>
</div>
);
}
value={name}으로 입력값을 state가 통제합니다.onChange가 없으면 입력이 안 바뀌는 읽기 전용처럼 동작하니 짝으로 씁니다.
💡 TIP —
e.target.value는 항상 문자열입니다. 숫자가 필요하면Number(e.target.value)로 변환하세요.
여러 입력을 한 핸들러로
입력마다 핸들러를 만들면 코드가 길어집니다. name 속성과 객체 state를 활용하면 하나로 끝납니다.
function SignupForm() {
const [form, setForm] = useState({ email: '', password: '' });
function handleChange(e) {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value })); // 계산된 키
}
return (
<form>
<input name="email" value={form.email} onChange={handleChange} />
<input name="password" value={form.password} onChange={handleChange} />
</form>
);
}
[name]: value 는 계산된 프로퍼티 이름으로, name 속성값에 해당하는 필드만 갱신합니다.
체크박스와 셀렉트
입력 종류마다 읽는 속성이 다릅니다.
function Options() {
const [agree, setAgree] = useState(false);
const [city, setCity] = useState('seoul');
return (
<form>
<label>
<input
type="checkbox"
checked={agree} // value가 아니라 checked
onChange={(e) => setAgree(e.target.checked)}
/>
약관 동의
</label>
<select value={city} onChange={(e) => setCity(e.target.value)}>
<option value="seoul">서울</option>
<option value="busan">부산</option>
</select>
</form>
);
}
| 입력 종류 | 묶는 속성 | 읽는 값 |
|---|---|---|
| text / textarea | value | e.target.value |
| checkbox | checked | e.target.checked |
| radio | checked | e.target.value |
| select | value | e.target.value |
폼 제출 처리
<form> 의 onSubmit 에서 preventDefault() 로 새로고침을 막고 원하는 로직을 실행합니다.
function SearchForm() {
const [query, setQuery] = useState('');
function handleSubmit(e) {
e.preventDefault(); // 기본 새로고침 막기
if (!query.trim()) return; // 간단한 유효성 검사
console.log('검색:', query);
}
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button type="submit">검색</button>
</form>
);
}
⚠️ 주의 —
preventDefault()를 안 부르면 폼이 제출되며 페이지가 새로고침돼 state가 초기화됩니다.
비제어 컴포넌트
state로 묶지 않고, 제출 순간에만 DOM에서 값을 읽는 방식도 있습니다. useRef 로 입력 요소를 참조합니다.
import { useRef } from 'react';
function UncontrolledForm() {
const inputRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
console.log(inputRef.current.value); // 제출 시점에만 읽음
}
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} defaultValue="" />
<button>제출</button>
</form>
);
}
- 매 입력마다 리렌더가 없어 간단하지만, 실시간 검증·동기화는 어렵습니다.
- 파일 입력(
<input type="file">)은 보안상 비제어로만 다룰 수 있습니다.
| 비교 | 제어 | 비제어 |
|---|---|---|
| 값의 출처 | state | DOM |
| 실시간 검증 | 쉬움 | 어려움 |
| 코드량 | 많음 | 적음 |
| 권장 | 대부분의 경우 | 단순/파일 입력 |
요약
- 제어 컴포넌트는
value+onChange로 입력을 state와 일치시킨다. - 여러 입력은
name속성과[name]: value로 한 핸들러에서 처리한다. - 체크박스는
checked, 셀렉트는value로 묶는다. - 제출은
onSubmit+e.preventDefault(). - 비제어 컴포넌트는
useRef로 제출 시점에만 값을 읽는다.
연습문제
- 이름·이메일·메시지 3개 입력을 가진 문의 폼을 하나의 핸들러로 처리하세요.
- "전체 동의" 체크박스 하나로 하위 체크박스 2개를 동시에 켜고 끄게 만드세요.
- 셀렉트로 고른 색상에 따라 박스 배경색이 바뀌도록 하세요.
- 제출 시 입력이 비어 있으면 "필수 항목입니다" 에러 메시지를 보여주세요.
useRef를 사용한 비제어 검색창을 만들어 제출 시 콘솔에 값을 출력하세요.
힌트 — 1번은 객체 state +
[name]: value. 4번은 별도errorstate를 두고 조건부 렌더링.
💡 연습문제 풀이
불러오는 중…
댓글 0
“React.js” 강좌에 대한 댓글입니다.