홍풍 프로젝트를 하면서 폼 검증이 항상 골칫거리였다. 특히 BasicInput
컴포넌트를 사용할 때마다 각각의 입력 필드에 대한 검증 로직을 일일이 작성해야 하는 번거로움이 있었다.
기존에는 단순히 useState
로 입력값을 관리하고, onBlur
나 onSubmit
시점에 검증 함수를 호출하는 방식이었다. 하지만 이 방식은 몇 가지 문제가 있었다:
이런 문제들을 해결하기 위해 Zod를 활용한 타입 안전한 폼 검증 훅을 만들어보기로 했다.
현재 BasicInput
컴포넌트는 다음과 같은 구조로 되어 있다:
type InputProps = {
label: string;
inputValue: string;
setInputValue: (text: string) => void;
validationCondition: ValidationState;
onBlur?: () => void;
onFocus?: () => void;
// ... 기타 props
};
export const BasicInput = forwardRef<TextInput, InputProps>(
({
label,
inputValue,
setInputValue,
validationCondition,
onBlur,
onFocus,
// ... 기타 props
}, ref) => {
// ... 컴포넌트 로직
return (
<View style={[styles.inputGroup]}>
<Animated.Text style={[styles.labelText, labelStyle]}>
{label}
{requireMark && <Text style={{ color: "red" }}>*</Text>}
</Animated.Text>
<TextInput
ref={ref}
style={[
styles.InputBox,
{
borderBottomColor:
validationCondition?.state !== "ERROR"
? underlineColor
: Color["red500"],
},
]}
value={inputValue}
onChangeText={setInputValue}
onFocus={onFocus}
onBlur={onBlur}
// ... 기타 props
/>
{validationCondition?.state === "ERROR" &&
validationCondition.errorText.length > 0 && (
<Text style={styles.errorText}>
{validationCondition?.errorText}
</Text>
)}
</View>
);
},
);
이 컴포넌트는 validationCondition
을 외부에서 받아서 에러 상태를 표시하는 구조다. 하지만 이 방식은 각 사용처에서 검증 로직을 일일이 작성해야 한다는 문제가 있다.
먼저 검증 상태를 관리하기 위한 타입을 정의했다:
export type ValidationState =
| {
state: "PENDING" | "BEFORE" | "VALID";
}
| {
state: "ERROR";
errorText: string;
};
이 타입은 검증의 4가지 상태를 표현한다:
BEFORE
: 아직 검증하지 않은 상태PENDING
: 검증 중인 상태 (사용자 입력 중이거나 비동기 검증 시)VALID
: 검증 통과 상태ERROR
: 검증 실패 상태 (에러 메시지 포함)특히 PENDING
상태는 두 가지 경우에 사용된다: