이번 프로젝트에서 제일 많이 다뤘던 라이브러리는 단연코 yup이랑 Formik이었다.
웹에서 사용하는 모든 form을 위 라이브러리를 적용해, 유효성 검사를 해주었다.
Yup & Formik를 쓰면 form 내의 state관리가 편하며, 스키마 기반 유효성 검사로, 보다 편리하게 양식 검사를 수행할 수 있다.
이전보다, 알게된 것이 많아진 것 같아 다시 한번 정리해보려고 한다.
핸드폰 유효성 검사
프로젝트 특성 상, 정규표현식을 사용하는 것보다, phone Validation 라이브러리를 사용하는게 마음이 편했다.
import "yup-phone";
yup에 적용가능한 yup-phone 라이브러리를 사용해줬다.
yup-phone github
phone: yup
.string()
.min(9, '핸드폰 번호가 너무 짧아요')
.max(15, '핸드폰 번호가 너무 길어요')
.required('핸드폰 번호는 필수 값입니다')
.phone('IN', false, '유효하지 않은 번호입니다.'),
위와 같이 작성해줬다.
참고로, string()으로 받아와야 length의 제한을 둘 수 있다.
Form에 페이지 전환이 있을 때 (Button - type : submit을 2번 클릭해야 할 때)
첫번째 페이지에서는 새로 만들 아이디 비밀번호를 입력하고,
두번째 페이지에서는 유저의 정보를 입력하고 최종적으로 post요청을 보낸다고 가정해보자.
yup schema는 다음과 같을 것 이다.
const initials = {
email: "",
password: "",
userInfo: {
name: "",
age: 0,
},
};
const schema = yup.object().shape({
email: yup
.string()
.required('이메일 값은 필수입니다.')
.email('이메일 형식이 아닙니다.')),
password: yup
.string()
.required('페스워드 값은 필수입니다.')
.matches(PasswordPattern, t('사용가능한 페스워드 값이 아닙니다.')),
userInfo :yup.object().shape({
name : yup.string('이름 값은 필수입니다.'),
age: yup.number().required('나이 값은 필수입니다.')
}),
})
첫번째 페이지에서도 유효성 검사가 이루어져야하고, 두번째 페이지에서도 유효성 검사가 이루어져야 한다.
그런데 위 스키마를 보면, 모든 데이터를 비교한다.
만약 첫번째 페이지에서 submit 버튼을 누르면, 아직 userInfo
값을 채우지 않아 submit버튼이 동작하지 않을 것이다.
- error :
userInfo
는 다음 페이지에서 검수되어야 한다.
- solution :
const schema = (step : string ) => yup.object().shape({
email: yup
.string()
.required('이메일 값은 필수입니다.')
.email('이메일 형식이 아닙니다.')),
password: yup
.string()
.required('페스워드 값은 필수입니다.')
.matches(PasswordPattern, t('사용가능한 페스워드 값이 아닙니다.')),
...(step == '2' && {userInfo : yup.object().shape({
name : yup.string('이름 값은 필수입니다.'),
age: yup.number().required('나이 값은 필수입니다.')
}),
})
schema에 매개변수로 두번째 페이지를 나타내는step
을 받아와, 조건부로 userInfo
를 받아오도록 해주었다.
참고 : [번역] 모든 프론트엔드 개발자가 써야 할 10가지 JavaScript 트릭
const handleSubmit = useCallback(async (values: any, {setTouched}: any) => {
const {
email,
password,
userInfo: {name, age}
} = values // 비구조화 할당
if (step === '1') {
setTouched({
company: {
name: false,
age: false
}
})
return await push({query: {step: 2}}, undefined, {shallow: true})
} else if (step === '2'){
await postAuth({
email,
password,
userInfo: {
name,
age
}
})
await push('/login')
}
...
}
submit 함수에서는 step 1단계에서 setTouched
false 처리를 해줘야 한다.
한번 submit버튼이 눌리면. form내의 모든 values가 touched true 처리 되기 때문에,
유저들은 두번째 페이지에서 아직 폼을 입력하지도 않았는데 에러메세지가 뜨기 때문이다.
Submit 버튼 중복 클릭 이슈 막기
return (
<>
// ...
<Button
type="submit"
onClick={form.handleSubmit}
disabled={!(form.isValid && form.dirty) || form.isSubmitting}
>
제출
</Button>
</>
);
submitButton이 disabled되는 상황은 다음과 같다.
!(form.isValid && form.dirty)
- form.isValid : True if state.errors is empty
- form 내에 에러메세지가 없으면 true
- form.dirty : True if any input has been touched. False otherwise.
- 만약 어떤 입력칸이 클릭(touched)되었을 때 true
- form.isSubmitting : whether the form is currently submitting
- form 현재 submitting 되고 있으면 true
즉, 현재 폼에 에러메세지가 있고, 그리고 그 폼들의 input들이 아직 touched 되지 않았을 때, 혹은 현재 submitting 중일 때, button이 disabled 되도록 했다.
FormikField component 만들기
export const TextField: React.FC<TextFieldProps> = ({ name, width }) => {
const form: FormikContextType<any> = useFormikContext();
return (
<>
<Style.InputWrapper width={width}>
<input
id={name}
defaultValue={form.values[name]}
name={name}
onChange={form.handleChange}
onBlur={form.handleBlur}
/>
</Style.InputWrapper>
<ErrorMessage
error={`${
form.touched[name] && form.errors[name] ? form.errors[name] : ""
}`}
/>
</>
);
};
Formik 라이브러리에서 제공하는 <Field/>
와 <ErrorMessage/>
를 활용해도 좋다.
직접 커스텀하고 싶은 경우 위와 같이 만들어줄 수도 있다.
Formik의 Validation Schema 함수로 사용하기
const schema = (t: Translate) =>
yup.object().shape({
userInfo: yup.object().shape({
name: yup.string().required(t("company.name.errors.required")),
tel: yup
.string()
.required(t("company.tel.errors.required"))
.min(9, t("company.tel.errors.min"))
.max(11, t("company.tel.errors.max")),
}),
});
formik에 넘겨주는 validationSchema 값은 기본은 object지만 넘기고 싶은 값이 있을 때, 함수로도 사용가능하다.
많이 찾아보고 작성한 글이나, 위 코드보다 더 좋은 방법이 있을 수도 있습니다.
틀리거나 개선할 부분이 있는 경우 댓글로 언제든지 조언해주시면 감사드립니다.
<이전 글>
https://kimyk60.tistory.com/14?category=1012783
'Front-end > Module' 카테고리의 다른 글
[react-daum-postcode] 정확한 주소값 받아오기 (0) | 2022.10.07 |
---|---|
[@react-google-maps/api] Google 지도에 Custom Marker 나타내기 (1) | 2022.09.01 |
[styled-components] TypeScript에서 전역 스타일(Global Style) 적용하기 (0) | 2022.06.29 |
[react-slick] dots css 커스텀하기 (0) | 2022.06.04 |
[Library] Iamport (0) | 2022.03.24 |