[이제와서 시작하는 React 마스터하기 #10] 폼 처리 - 사용자 입력 받기
[이제와서 시작하는 React 마스터하기 #10] 폼 처리 - 사용자 입력 받기
React Day 10 - 사용자가 입력한 데이터를 받아서 처리하고 싶으신가요? React에서 폼을 다루는 방법을 배워봅시다!
오늘 배울 내용
- ✅ 폼이란 무엇인가?
- ✅ Controlled Component: React로 입력 관리하기
- ✅ 기본 유효성 검증 (validation)
- ✅ 실전 예제: 회원가입 폼, 연락처 폼
시작하기 전에
이전에 배운 내용을 기억하시나요?
- Day 3: useState에서 상태 관리를 배웠어요
- Day 6: 조건부 렌더링에서 조건에 따라 화면을 바꿨어요
- Day 9: React Router에서 페이지 이동을 구현했어요
오늘은 사용자 입력을 받고 검증하는 방법을 배워봅시다!
폼(Form)이란?
폼은 사용자로부터 정보를 입력받는 화면이에요.
일상 비유 📝
설문지를 생각해보세요:
1
2
3
4
5
6
[설문지]
이름: ___________
이메일: ___________
나이: ___________
[제출] 버튼
웹사이트의 폼도 똑같아요:
- 로그인 폼 (아이디, 비밀번호)
- 회원가입 폼 (이름, 이메일, 비밀번호)
- 연락처 폼 (이름, 이메일, 메시지)
- 검색 폼 (검색어)
기본 폼 만들기
일반 HTML 폼 (React 없이)
1
2
3
4
<form>
<input type="text" placeholder="이름" />
<button type="submit">제출</button>
</form>
문제점: React가 입력 값을 모르기 때문에 관리가 어려워요!
React 방식: Controlled Component
React에서는 state로 입력 값을 관리해요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { useState } from 'react';
function App() {
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // 페이지 새로고침 방지
alert(`안녕하세요, ${name}님!`);
};
return (
<div style={{ padding: '40px' }}>
<h1>간단한 폼</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="이름을 입력하세요"
style={{
padding: '10px',
fontSize: '16px',
width: '300px',
border: '2px solid #ddd',
borderRadius: '5px'
}}
/>
<button
type="submit"
style={{
marginLeft: '10px',
padding: '10px 20px',
fontSize: '16px',
backgroundColor: '#4caf50',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
제출
</button>
</form>
<p style={{ marginTop: '20px' }}>
입력한 이름: <strong>{name}</strong>
</p>
</div>
);
}
export default App;
동작 원리:
useState로 name 상태 생성value={name}: input의 값을 state로 관리onChange={(e) => setName(e.target.value)}: 입력할 때마다 state 업데이트onSubmit: 제출 버튼을 누르면 실행
왜 e.preventDefault()?
- 기본적으로 form 제출하면 페이지가 새로고침됨
- 이걸 막아야 React가 제대로 작동해요!
여러 개의 입력 필드
여러 input을 관리할 때는 각각 state를 만들어요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { useState } from 'react';
function App() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('제출된 데이터:', { name, email, message });
alert('메시지가 전송되었습니다!');
// 폼 초기화
setName('');
setEmail('');
setMessage('');
};
return (
<div style={{ padding: '40px', maxWidth: '500px', margin: '0 auto' }}>
<h1>📧 연락처 폼</h1>
<form onSubmit={handleSubmit}>
{/* 이름 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
이름
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="홍길동"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
</div>
{/* 이메일 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
이메일
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="hong@example.com"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
</div>
{/* 메시지 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
메시지
</label>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="메시지를 입력하세요"
rows={5}
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px',
boxSizing: 'border-box',
fontFamily: 'inherit'
}}
/>
</div>
{/* 제출 버튼 */}
<button
type="submit"
style={{
width: '100%',
padding: '12px',
fontSize: '18px',
backgroundColor: '#2196f3',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
전송하기
</button>
</form>
{/* 입력 미리보기 */}
<div
style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#f5f5f5',
borderRadius: '8px'
}}
>
<h3>입력 미리보기</h3>
<p><strong>이름:</strong> {name || '(입력되지 않음)'}</p>
<p><strong>이메일:</strong> {email || '(입력되지 않음)'}</p>
<p><strong>메시지:</strong> {message || '(입력되지 않음)'}</p>
</div>
</div>
);
}
export default App;
해보기:
- 각 필드에 값을 입력해보세요
- “입력 미리보기”가 실시간으로 업데이트되는지 확인하세요
- “전송하기” 버튼을 눌러보세요
유효성 검증 (Validation)
사용자가 올바른 정보를 입력했는지 확인하는 거예요!
기본 검증: 필수 입력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { useState } from 'react';
function App() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// 검증: 빈 값 체크
if (!email || !password) {
setError('모든 필드를 입력해주세요!');
return;
}
// 검증: 이메일 형식 체크
if (!email.includes('@')) {
setError('올바른 이메일 형식이 아닙니다!');
return;
}
// 검증: 비밀번호 길이 체크
if (password.length < 6) {
setError('비밀번호는 6자 이상이어야 합니다!');
return;
}
// 검증 통과!
setError('');
alert('로그인 성공!');
};
return (
<div style={{ padding: '40px', maxWidth: '400px', margin: '0 auto' }}>
<h1>🔐 로그인</h1>
<form onSubmit={handleSubmit}>
{/* 에러 메시지 */}
{error && (
<div
style={{
padding: '10px',
marginBottom: '20px',
backgroundColor: '#ffebee',
border: '1px solid #f44336',
borderRadius: '5px',
color: '#c62828'
}}
>
⚠️ {error}
</div>
)}
{/* 이메일 */}
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
이메일
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="example@email.com"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
</div>
{/* 비밀번호 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
비밀번호
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="6자 이상 입력"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
fontSize: '18px',
backgroundColor: '#2196f3',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
로그인
</button>
</form>
</div>
);
}
export default App;
검증 규칙:
- ✅ 빈 값이면 안 됨
- ✅ 이메일은
@포함해야 함 - ✅ 비밀번호는 6자 이상이어야 함
해보기:
- 빈 값으로 제출해보세요
- 잘못된 이메일 형식으로 입력해보세요
- 비밀번호를 5자만 입력해보세요
- 올바르게 입력해서 성공 메시지를 확인하세요!
실시간 유효성 검증
입력하는 동시에 검증하는 방법이에요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { useState } from 'react';
function App() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// 각 필드별 에러 메시지
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const handleEmailChange = (e) => {
const value = e.target.value;
setEmail(value);
// 실시간 검증
if (!value) {
setEmailError('이메일을 입력해주세요');
} else if (!value.includes('@')) {
setEmailError('올바른 이메일 형식이 아닙니다');
} else {
setEmailError('');
}
};
const handlePasswordChange = (e) => {
const value = e.target.value;
setPassword(value);
// 실시간 검증
if (!value) {
setPasswordError('비밀번호를 입력해주세요');
} else if (value.length < 6) {
setPasswordError(`${6 - value.length}자 더 입력해야 합니다`);
} else {
setPasswordError('');
}
};
const handleSubmit = (e) => {
e.preventDefault();
if (emailError || passwordError || !email || !password) {
alert('입력 내용을 확인해주세요!');
return;
}
alert('로그인 성공!');
};
const isFormValid = email && password && !emailError && !passwordError;
return (
<div style={{ padding: '40px', maxWidth: '400px', margin: '0 auto' }}>
<h1>🔐 실시간 검증 로그인</h1>
<form onSubmit={handleSubmit}>
{/* 이메일 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
이메일
</label>
<input
type="email"
value={email}
onChange={handleEmailChange}
placeholder="example@email.com"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${emailError ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{emailError && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{emailError}
</p>
)}
</div>
{/* 비밀번호 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
비밀번호
</label>
<input
type="password"
value={password}
onChange={handlePasswordChange}
placeholder="6자 이상"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${passwordError ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{passwordError && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{passwordError}
</p>
)}
</div>
<button
type="submit"
disabled={!isFormValid}
style={{
width: '100%',
padding: '12px',
fontSize: '18px',
backgroundColor: isFormValid ? '#2196f3' : '#ccc',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: isFormValid ? 'pointer' : 'not-allowed',
fontWeight: 'bold'
}}
>
로그인
</button>
</form>
{/* 검증 상태 표시 */}
<div
style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#f5f5f5',
borderRadius: '8px'
}}
>
<h3>검증 상태</h3>
<p>
이메일: {email && !emailError ? '✅ 올바름' : '❌'}
</p>
<p>
비밀번호: {password && !passwordError ? '✅ 올바름' : '❌'}
</p>
</div>
</div>
);
}
export default App;
특징:
- ✅ 입력하는 순간 바로 검증
- ✅ 에러가 있으면 input 테두리 빨간색
- ✅ 모든 검증 통과해야 버튼 활성화
실습: 회원가입 폼
실전에서 많이 사용하는 회원가입 폼을 만들어봅시다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import { useState } from 'react';
function App() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: '',
agree: false
});
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
});
};
const validate = () => {
const newErrors = {};
// 사용자명 검증
if (!formData.username) {
newErrors.username = '사용자명을 입력해주세요';
} else if (formData.username.length < 3) {
newErrors.username = '사용자명은 3자 이상이어야 합니다';
}
// 이메일 검증
if (!formData.email) {
newErrors.email = '이메일을 입력해주세요';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '올바른 이메일 형식이 아닙니다';
}
// 비밀번호 검증
if (!formData.password) {
newErrors.password = '비밀번호를 입력해주세요';
} else if (formData.password.length < 8) {
newErrors.password = '비밀번호는 8자 이상이어야 합니다';
}
// 비밀번호 확인 검증
if (!formData.confirmPassword) {
newErrors.confirmPassword = '비밀번호 확인을 입력해주세요';
} else if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = '비밀번호가 일치하지 않습니다';
}
// 약관 동의 검증
if (!formData.agree) {
newErrors.agree = '약관에 동의해주세요';
}
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = validate();
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
console.log('회원가입 데이터:', formData);
alert('회원가입 성공!');
// 폼 초기화
setFormData({
username: '',
email: '',
password: '',
confirmPassword: '',
agree: false
});
}
};
return (
<div style={{ padding: '40px', maxWidth: '500px', margin: '0 auto' }}>
<h1>👤 회원가입</h1>
<form onSubmit={handleSubmit}>
{/* 사용자명 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
사용자명 *
</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="3자 이상"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${errors.username ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{errors.username && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{errors.username}
</p>
)}
</div>
{/* 이메일 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
이메일 *
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="example@email.com"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${errors.email ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{errors.email && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{errors.email}
</p>
)}
</div>
{/* 비밀번호 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
비밀번호 *
</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="8자 이상"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${errors.password ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{errors.password && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{errors.password}
</p>
)}
</div>
{/* 비밀번호 확인 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
비밀번호 확인 *
</label>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
placeholder="비밀번호 재입력"
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: `2px solid ${errors.confirmPassword ? '#f44336' : '#ddd'}`,
borderRadius: '5px',
boxSizing: 'border-box'
}}
/>
{errors.confirmPassword && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{errors.confirmPassword}
</p>
)}
</div>
{/* 약관 동의 */}
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
name="agree"
checked={formData.agree}
onChange={handleChange}
style={{ marginRight: '10px', width: '18px', height: '18px' }}
/>
<span>이용약관 및 개인정보 처리방침에 동의합니다 *</span>
</label>
{errors.agree && (
<p style={{ margin: '5px 0 0 0', color: '#f44336', fontSize: '14px' }}>
{errors.agree}
</p>
)}
</div>
{/* 제출 버튼 */}
<button
type="submit"
style={{
width: '100%',
padding: '15px',
fontSize: '18px',
backgroundColor: '#4caf50',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
회원가입
</button>
</form>
{/* 입력 데이터 미리보기 */}
<div
style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#f5f5f5',
borderRadius: '8px'
}}
>
<h3>입력 데이터</h3>
<p><strong>사용자명:</strong> {formData.username || '(없음)'}</p>
<p><strong>이메일:</strong> {formData.email || '(없음)'}</p>
<p><strong>비밀번호:</strong> {formData.password ? '●'.repeat(formData.password.length) : '(없음)'}</p>
<p><strong>약관 동의:</strong> {formData.agree ? '✅ 동의함' : '❌ 미동의'}</p>
</div>
</div>
);
}
export default App;
해보기:
- 모든 필드를 올바르게 입력해보세요
- 각 검증 규칙을 위반해보세요 (짧은 사용자명, 잘못된 이메일, 비밀번호 불일치)
- 약관 동의를 체크하지 않고 제출해보세요
- 모든 검증을 통과해서 성공 메시지를 확인하세요!
Select와 Radio 버튼
Select (드롭다운)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const [country, setCountry] = useState('');
<select
value={country}
onChange={(e) => setCountry(e.target.value)}
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #ddd',
borderRadius: '5px'
}}
>
<option value="">국가 선택</option>
<option value="kr">대한민국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
Radio 버튼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const [gender, setGender] = useState('');
<div>
<label style={{ marginRight: '20px' }}>
<input
type="radio"
name="gender"
value="male"
checked={gender === 'male'}
onChange={(e) => setGender(e.target.value)}
style={{ marginRight: '5px' }}
/>
남성
</label>
<label>
<input
type="radio"
name="gender"
value="female"
checked={gender === 'female'}
onChange={(e) => setGender(e.target.value)}
style={{ marginRight: '5px' }}
/>
여성
</label>
</div>
흔한 실수와 해결 방법
실수 1: e.preventDefault() 빼먹기
❌ 잘못된 코드:
1
2
3
4
5
const handleSubmit = (e) => {
// e.preventDefault() 없음!
alert('제출!');
// 페이지가 새로고침됨!
};
✅ 올바른 코드:
1
2
3
4
const handleSubmit = (e) => {
e.preventDefault(); // 필수!
alert('제출!');
};
실수 2: value 없이 onChange만 사용
❌ 잘못된 코드:
1
2
3
4
<input
onChange={(e) => setName(e.target.value)}
// value 속성이 없어요!
/>
✅ 올바른 코드:
1
2
3
4
<input
value={name} // 필수!
onChange={(e) => setName(e.target.value)}
/>
실수 3: 체크박스를 value로 처리
❌ 잘못된 코드:
1
2
3
4
5
<input
type="checkbox"
value={agree} // 체크박스는 value가 아니라 checked!
onChange={(e) => setAgree(e.target.value)}
/>
✅ 올바른 코드:
1
2
3
4
5
<input
type="checkbox"
checked={agree} // checked 사용!
onChange={(e) => setAgree(e.target.checked)} // .checked 사용!
/>
정리
오늘은 React에서 폼을 다루는 방법을 배웠어요!
핵심 포인트 ✅
Controlled Component:
value={state}: input의 값을 state로 관리onChange: 입력할 때마다 state 업데이트- React가 입력 값을 완전히 제어
유효성 검증:
- 제출할 때 검증:
handleSubmit에서 체크 - 실시간 검증:
onChange에서 체크 - 에러 메시지 표시: 조건부 렌더링
폼 타입:
<input type="text">: 텍스트 입력<input type="email">: 이메일 입력<input type="password">: 비밀번호 입력<input type="checkbox">: 체크박스<input type="radio">: 라디오 버튼<select>: 드롭다운<textarea>: 여러 줄 텍스트
오늘 배운 내용 체크 ✅
- Controlled Component가 무엇인지 이해했나요?
- e.preventDefault()의 역할을 알고 있나요?
- 여러 개의 input을 관리할 수 있나요?
- 기본 유효성 검증을 구현할 수 있나요?
- 회원가입 폼을 만들어봤나요?
숙제 📚
- 설문조사 폼: 이름, 나이, 성별, 관심사(체크박스), 의견(textarea)
- 예약 폼: 날짜, 시간, 인원 수, 특별 요청사항
- 프로필 수정 폼: 사진(파일), 자기소개, SNS 링크
다음 단계
다음 포스트부터는 좀 더 고급 주제들을 다뤄요. 하지만 지금까지 배운 기초가 탄탄하면 충분히 이해할 수 있어요!
“늦었다고 생각할 때가 가장 빠를 때입니다. React 폼 마스터! 🎉”
React 완벽 가이드 시리즈
- React란 무엇인가?
- 첫 React 앱 만들기
- useState - 클릭하면 바뀌는 화면
- Props - 컴포넌트끼리 대화하기
- useEffect - 컴포넌트 생명주기
- 조건부/리스트 렌더링
- Context API - Props 지옥에서 탈출
- Custom Hooks - 반복되는 로직 재사용하기
- React Router - 페이지 이동하기
- 폼 처리 - 사용자 입력 받기 ← 현재 글
- React 성능 최적화
- 에러 바운더리와 Suspense
- 상태 관리 라이브러리
- 서버 상태 관리
- React와 TypeScript
- Next.js 입문
- React 테스팅 전략
- 고급 패턴과 베스트 프랙티스
- 실전 프로젝트
- React 배포와 DevOps
관련 자료
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.
