폼 관리와 유효성 검증 — React Hook Form 활용
회원가입, 결제 같은 복잡한 폼에서는 React Hook Form으로 성능과 유효성 검증을 동시에 잡을 수 있습니다.
모바일 앱에서 폼은 가장 중요한 사용자 입력 인터페이스입니다. useState로 각 필드를 관리하면 코드가 금방 복잡해집니다.
설치
npm install react-hook-form zod @hookform/resolvers
기본 사용법
import { useForm, Controller } from 'react-hook-form';
import { View, TextInput, Text, Pressable, StyleSheet } from 'react-native';
interface LoginForm {
email: string;
password: string;
}
function LoginScreen() {
const { control, handleSubmit, formState: { errors, isSubmitting } } = useForm<LoginForm>({
defaultValues: { email: '', password: '' },
});
const onSubmit = async (data: LoginForm) => {
console.log('폼 데이터:', data);
await loginAPI(data);
};
return (
<View style={styles.container}>
<Controller
control={control}
name="email"
rules={{
required: '이메일을 입력하세요',
pattern: { value: /^\S+@\S+$/i, message: '올바른 이메일 형식이 아닙니다' },
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.email && styles.errorInput]}
value={value}
onChangeText={onChange}
onBlur={onBlur}
placeholder="이메일"
keyboardType="email-address"
autoCapitalize="none"
/>
)}
/>
{errors.email && <Text style={styles.error}>{errors.email.message}</Text>}
<Controller
control={control}
name="password"
rules={{
required: '비밀번호를 입력하세요',
minLength: { value: 8, message: '8자 이상 입력하세요' },
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.password && styles.errorInput]}
value={value}
onChangeText={onChange}
onBlur={onBlur}
placeholder="비밀번호"
secureTextEntry
/>
)}
/>
{errors.password && <Text style={styles.error}>{errors.password.message}</Text>}
<Pressable
style={styles.button}
onPress={handleSubmit(onSubmit)}
disabled={isSubmitting}
>
<Text style={styles.buttonText}>{isSubmitting ? '로그인 중...' : '로그인'}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 20 },
input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, marginBottom: 4, fontSize: 16 },
errorInput: { borderColor: 'red' },
error: { color: 'red', fontSize: 12, marginBottom: 12 },
button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, alignItems: 'center', marginTop: 16 },
buttonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
});
Zod 스키마로 유효성 검증
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
const signUpSchema = z.object({
name: z.string().min(2, '이름은 2자 이상이어야 합니다'),
email: z.string().email('올바른 이메일 형식이 아닙니다'),
password: z.string()
.min(8, '8자 이상 입력하세요')
.regex(/[A-Z]/, '대문자를 포함하세요')
.regex(/[0-9]/, '숫자를 포함하세요'),
confirmPassword: z.string(),
phone: z.string().regex(/^010-\d{4}-\d{4}$/, '올바른 전화번호 형식이 아닙니다'),
}).refine((data) => data.password === data.confirmPassword, {
message: '비밀번호가 일치하지 않습니다',
path: ['confirmPassword'],
});
type SignUpForm = z.infer<typeof signUpSchema>;
function SignUpScreen() {
const { control, handleSubmit, formState: { errors } } = useForm<SignUpForm>({
resolver: zodResolver(signUpSchema),
defaultValues: { name: '', email: '', password: '', confirmPassword: '', phone: '' },
});
return (
<View>
{/* Controller로 각 필드 연결 */}
</View>
);
}
재사용 가능한 Input 컴포넌트
import { Control, Controller, FieldValues, Path } from 'react-hook-form';
interface FormInputProps<T extends FieldValues> {
control: Control<T>;
name: Path<T>;
label: string;
placeholder?: string;
secureTextEntry?: boolean;
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad';
}
function FormInput<T extends FieldValues>({
control, name, label, placeholder, secureTextEntry, keyboardType,
}: FormInputProps<T>) {
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value }, fieldState: { error } }) => (
<View style={{ marginBottom: 16 }}>
<Text style={styles.label}>{label}</Text>
<TextInput
style={[styles.input, error && styles.errorInput]}
value={value}
onChangeText={onChange}
onBlur={onBlur}
placeholder={placeholder}
secureTextEntry={secureTextEntry}
keyboardType={keyboardType}
/>
{error && <Text style={styles.error}>{error.message}</Text>}
</View>
)}
/>
);
}
// 사용
<FormInput control={control} name="email" label="이메일" keyboardType="email-address" />
<FormInput control={control} name="password" label="비밀번호" secureTextEntry />
정리
- React Hook Form 은 불필요한 리렌더링 없이 폼 상태를 효율적으로 관리합니다
- Zod 와 결합하면 타입 안전한 유효성 검증 스키마를 정의할 수 있습니다
Controller컴포넌트로 React Native의TextInput과 연결합니다- 재사용 가능한
FormInput컴포넌트 를 만들면 폼 코드가 깔끔해집니다 - 폼 제출 시
handleSubmit이 유효성 검증을 자동으로 수행합니다
댓글 로딩 중...