Yup + formik ⇨ form state 관리 validation 검증Frontend/리액트 개발 꿀팁2021. 9. 9. 17:00
Table of Contents
Yup?
- Yup은 Form의 validation 검사를 하기 위한 라이브러리이다.
- 특히, 리액트에서 formik + yup을 사용해서 dynamic form과 validation을 쉽게 할 수 있다.
- Yup은 yup formatted object(우리의 데이터와 닮은 객체)를 생성한 후, utility function을 통해서 우리의 데이터와 formatted object가 매치되는지 확인하며 validation을 확인한다.
- 만약 사용자가 아래와 같은 양식처럼 데이터를 전송해야 한다고 하자.
{
name: string [필수값],
age: int [필수값],
email: email [필수값],
}
- 이를 검증할 yup schema object는 아래와 같다.
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
- Yup은 string, integer, date, boolean, array, object와 같은 기본적인 타입과 required(꼭 포함되어야 할 값), email, positive와 같은 값들도 검증해준다
- 만약, default 값을 정해주고 싶으면 위의 timpstamp처럼 default 값을 정할 수 있고 이 함수 내에서는 어떠한 로직이나 어떠한 값도 리턴될 수 있다!
- 또한, height 부분의 typeError는 숫자만 쳐야하는데 다른 값이 있으면 검증해준다! 수를 검증할 땐 typeError를 써주자!
validate()
- validate()는 데이터와 yup object의 검증결과를 프로미스로 리턴한다.
- 아래 예시를 보고 이해해보자!
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
let user_data = {
name: "duckgugong",
age: 26,
email: "qkwl521@naver.com",
}
//검증하기
validationSchema.validate(user_data)
.then((data)=>{/*검증에 성공했을 때*/})
.catch((err)=>{/*검증에 실패했을 때*/})
formik + yup
- 우선 아래 명령어로 yup을 설치하자
yarn add yup
- yup을 이용해서 formik의 각 폼에 대해 유효성 검사를 할 수 있다!
- Formik 컴포넌트의 validationSchema prop에 yup formatted object를 넣어줘서 validation을 진행할 수 있다.
- yup formatted object의 각 key 값들이 Formik의 상태에 대응되기 때문이다.
- formik에서는 기본적으로 type이 submit이 버튼을 클릭할 때 formik의 state를 yup으로 검증한다.
- 만약, 검증에 실패하면 onSubmit 이벤트가 발생하지 않는다!!
import React from "react";
import {Formik} from "formik";
import * as Yup from "yup";
import {Button, Input} from "@mui/material";
import "./App.css";
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
const App = () => (
<Formik
initialValues={{
name: "",
age: "",
email: "",
}}
validationSchema={validationSchema}
onSubmit={(data) => {
alert(JSON.stringify(data, null, 2));
}}
>
{({handleSubmit, handleChange, values, errors}) => (
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div className="input-wrapper">
<div>이름</div>
<Input name="name" value={values.name} onChange={handleChange} autoComplete="off"/>
</div>
<div className="input-wrapper">
<div>나이</div>
<Input name="age" value={values.height} onChange={handleChange} autoComplete="off"/>
</div>
<div className="input-wrapper">
<div>이메일</div>
<Input name="email" value={values.email} onChange={handleChange} autoComplete="off"/>
</div>
<Button
onClick={
() => {
validationSchema.validate(values)
.catch((err) => alert("올바른 값을 입력하세요!"))
}
}
type="submit"
variant="outlined"
>
제출하기
</Button>
</form>
</div>
)}
</Formik>
);
export default App;
validation 검증 & error message
- yup으로 validation schema를 작성할 때 적어놓은 조건을 만족하지 않으면 validation error가 발생해서 error message를 띄울 수 있다.
- 만약 name에 해당하는 input이 문자가 아니라면 '문자여야 합니다!'라는 error message를 사용자에게 보여줄 수 있고 아무 값도 입력하지 않았다면 '빈값 X'라는 error message를 보여줄 수 있다.
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
- 대략적인 사용법은 Formik 컴포넌트에서 input이나 엘리먼트에 errors라는 prop을 넘겨주고 검증한 결과 오류가 있는지 알려준다. 아래에서 자세한 예시들을 살펴보자
※중요※
- Formik에 아무런 설정을 해두지 않으면 formik의 value와 연결된 input에 onChange 이벤트가 발생하거나 submit 버튼을 클릭하면 formik value 전체에 대한 validation 검증을 한다.
import React from "react";
import {Formik, ErrorMessage} from "formik";
import * as Yup from "yup";
import {Button, Input} from "@mui/material";
import "./App.css";
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
const App = () => (
<Formik
initialValues={{
name: "",
age: "",
email: "",
}}
validationSchema={validationSchema}
onSubmit={(data) => {
alert(JSON.stringify(data, null, 2));
}}
>
{({handleSubmit, handleChange, values, errors}) => (
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div className="input-wrapper">
<div>이름</div>
<Input name="name" value={values.name} onChange={handleChange} autoComplete="off"/>
{/*error-message, submit이나 onChange 하면 valid하지 않은 input에 나타남*/}
<div className="error-message">{errors.name}</div>
</div>
<div className="input-wrapper">
<div>나이</div>
<Input name="age" value={values.height} onChange={handleChange} autoComplete="off"/>
<div className="error-message">{errors.age}</div>
</div>
<div className="input-wrapper">
<div>이메일</div>
<Input name="email" value={values.email} onChange={handleChange} autoComplete="off"/>
<div className="error-message">{errors.email}</div>
</div>
<Button
onClick={
() => {
validationSchema.validate(values)
.catch((err) => alert("올바른 값을 입력하세요!"))
}
}
type="submit"
variant="outlined"
>
제출하기
</Button>
</form>
</div>
)}
</Formik>
);
export default App;
- formik의 value에 대한 validation 검증이 한번 발생하면 validation 검증이 발생한 formik의 value와 연결되어 있는 input이나 엘리먼트가 valid할 때까지 계속 추적한다.
- 예를 들어 submit 버튼을 클릭해서 formik의 value 전체에 대한 validation 검증을 하고 error message를 보여주고 input에 validation schema에 맞는 값을 입력할 때까지 error message를 보여준다.
1. submit이 일어났을 때 error message 출력
- submit이 일어났을 때 formik의 value 전체에 대한 error message를 핸들링 하는 것이 가장 기본적인 사용법이다.
- <ErrorMessage> 컴포넌트를 이용하면 submit이 되었을 때만 error message를 띄울 수 있다.
- name이란 prop에 formik value의 name만 넣으면 됨!
- 아래처럼 쓰면 email이란 value에 대한 error message를 보여주는 것이다.
<ErrorMessage name="email"/>
- submit 버튼을 클릭해서 <ErrorMessage> 컴포넌트로 error message를 띄우면 formik의 value가 valid할 때 까지 계속 error message를 보여줄 수 있다.
import React from "react";
import {Formik, ErrorMessage} from "formik";
import * as Yup from "yup";
import {Button, Input} from "@mui/material";
import "./App.css";
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
const App = () => (
<Formik
initialValues={{
name: "",
age: "",
email: "",
}}
validationSchema={validationSchema}
onSubmit={(data) => {
alert(JSON.stringify(data, null, 2));
}}
>
{({handleSubmit, handleChange, values, errors}) => (
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div className="input-wrapper">
<div>이름</div>
<Input name="name" value={values.name} onChange={handleChange} autoComplete="off"/>
{/*error-message, submit을 하면 valid하지 않은 input에 나타남*/}
<div className="error-message">
<ErrorMessage name="name"/>
</div>
</div>
<div className="input-wrapper">
<div>나이</div>
<Input name="age" value={values.height} onChange={handleChange} autoComplete="off"/>
<div className="error-message">
<ErrorMessage name="age"/>
</div>
</div>
<div className="input-wrapper">
<div>이메일</div>
<Input name="email" value={values.email} onChange={handleChange} autoComplete="off"/>
<div className="error-message">
<ErrorMessage name="email"/>
</div>
</div>
<Button
onClick={
() => {
validationSchema.validate(values)
.catch((err) => alert("올바른 값을 입력하세요!"))
}
}
type="submit"
variant="outlined"
>
제출하기
</Button>
</form>
</div>
)}
</Formik>
);
export default App;
2. input에 onBlur, onClick 이벤트가 일어날 때 error message 출력
- Formik의 props 중 touched은 name에 상태의 키 값을 지정한 요소가 클릭되었는지 아닌지 true, false로 리턴한다.
- 만약 name이 "duck"인 인풋이 클릭이 되었으면, touched.duck이 true가 될 것이다.
- 이를 사용하기 위해 인풋의 onBlur나 onClick에 formik의 props인 handleBlur를 넣어주면 된다!
- handleBlur를 사용하는 input이 onBlur나 onClick 이벤트가 일어나면 formik의 모든 value가 검증된다.
import React from "react";
import {Formik} from "formik";
import * as Yup from "yup";
import {Button, Input} from "@mui/material";
import "./App.css";
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
const App = () => (
<Formik
initialValues={{
name: "",
age: "",
email: "",
}}
validationSchema={validationSchema}
onSubmit={(data) => {
alert(JSON.stringify(data, null, 2));
}}
>
{({handleSubmit, handleChange, values, errors, touched, handleBlur}) => (
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div className="input-wrapper">
<div>이름</div>
<Input name="name" value={values.name} onChange={handleChange} autoComplete="off"/>
{/*얘는 밑에 두개중 하나가 onClick 되거나 onBlur되면 자동으로 error message가 나옴*/}
<div className="error-message">{errors.name}</div>
</div>
<div className="input-wrapper">
<div>나이</div>
<Input name="age" value={values.height} onChange={handleChange} autoComplete="off" onClick={handleBlur}/>
{touched.age && <div className="error-message">{errors.age}</div> }
</div>
<div className="input-wrapper">
<div>이메일</div>
<Input name="email" value={values.email} onChange={handleChange} autoComplete="off" onBlur={handleBlur}/>
{touched.email && <div className="error-message">{errors.email}</div> }
</div>
<Button
onClick={
() => {
validationSchema.validate(values)
.catch((err) => alert("올바른 값을 입력하세요!"))
}
}
type="submit"
variant="outlined"
>
제출하기
</Button>
</form>
</div>
)}
</Formik>
);
export default App;
3. formik이 마운트 될때 바로 validation 검증
- submit 버튼을 클릭하지 않아도 마운트 되자마자 검증되어 오류메시지 띄움!
- formik이 마운트 되자마자 formik value에 대한 validation 검증을 한다.
- 마운트 되자마자 validation 검증을 하므로 formik의 value가 valid할 때 까지 추적하면서 error message를 보여주거나 특정 버튼들을 막아둘 수 있다.
- 이 방법을 사용하면 따로 submit 버튼이나 onClick, onBlur 이벤트를 사용하지 않아도 바로 error message를 valid할 때 까지 보여줄 수 있다.
- 예를 들어, 마운트 되자마자 error message를 보여주고 valid할 때 까지 error message를 보여줄수 있음
- Formik 컴포넌트의 옵션에 validateOnMount를 true로 주면 간단히 해결 가능!
import React from "react";
import {Formik} from "formik";
import * as Yup from "yup";
import {Button, Input} from "@mui/material";
import "./App.css";
const validationSchema = Yup.object().shape({
name: Yup
.string('문자여야 합니다!')
.required('빈값 X'),
age: Yup
.number()
.typeError('숫자만 입력하세요')
.integer()
.positive('양수만 입력하세요')
.required('빈값 X'),
email: Yup
.string()
.email('이메일 양식이 틀렸습니다!')
.required('빈값 X'),
});
const App = () => (
<Formik
initialValues={{
name: "",
age: "",
email: "",
}}
validationSchema={validationSchema}
onSubmit={(data) => {
alert(JSON.stringify(data, null, 2));
}}
// formik이 mount 되자마자 validation 검증
validateOnMount={true}
>
{({handleSubmit, handleChange, values, errors}) => (
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div className="input-wrapper">
<div>이름</div>
<Input name="name" value={values.name} onChange={handleChange} autoComplete="off"/>
{/*error-message, submit을 하면 나타남*/}
<div className="error-message">{errors.name}</div>
</div>
<div className="input-wrapper">
<div>나이</div>
<Input name="age" value={values.height} onChange={handleChange} autoComplete="off"/>
<div className="error-message">{errors.age}</div>
</div>
<div className="input-wrapper">
<div>이메일</div>
<Input name="email" value={values.email} onChange={handleChange} autoComplete="off"/>
<div className="error-message">{errors.email}</div>
</div>
<Button
onClick={
() => {
validationSchema.validate(values)
.catch((err) => alert("올바른 값을 입력하세요!"))
}
}
type="submit"
variant="outlined"
>
제출하기
</Button>
</form>
</div>
)}
</Formik>
);
export default App;
++ 자주 사용하는 회원가입 템플릿!!
- 비밀번호에 특수문자, 숫자 하나이상 무조건 포함!
- 닉네임에 특수문자 포함 금지!
import * as Yup from "yup";
import axios from "axios";
import { toast, ToastContainer } from "react-toastify";
import { Formik, ErrorMessage } from "formik";
import "../../pages/sign-up/signUp.scss";
import "react-toastify/dist/ReactToastify.css";
import { Button, TextField } from "@material-ui/core";
import { useNavigate } from "react-router-dom";
const SignUp = () => {
const navigate = useNavigate();
const validationSchema = Yup.object().shape({
email: Yup.string()
.email("올바른 이메일 형식이 아닙니다!")
.required("이메일을 입력하세요!"),
username: Yup.string()
.min(2, "닉네임은 최소 2글자 이상입니다!")
.max(10, "닉네임은 최대 10글자입니다!")
.matches(
/^[가-힣a-zA-Z][^!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\s]*$/,
"닉네임에 특수문자가 포함되면 안되고 숫자로 시작하면 안됩니다!"
)
.required("닉네임을 입력하세요!"),
password: Yup.string()
.min(8, "비밀번호는 최소 8자리 이상입니다")
.max(16, "비밀번호는 최대 16자리입니다!")
.required("패스워드를 입력하세요!")
.matches(
/^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])[^\s]*$/,
"알파벳, 숫자, 공백을 제외한 특수문자를 모두 포함해야 합니다!"
),
password2: Yup.string()
.oneOf([Yup.ref("password"), null], "비밀번호가 일치하지 않습니다!")
.required("필수 입력 값입니다!"),
});
const submit = async (values) => {
const { email, password, username } = values;
try {
await axios.post("http://13.125.145.83/api/register", {
email,
password,
username,
});
toast.success("회원등록 완료하였습니다. 로그인 하세요😎", {
position: "top-center",
autoClose: 2000,
});
window.setTimeout(() => {
navigate("/login");
}, 2000);
} catch (e) {
// 서버에서 받은 에러 메시지 출력
toast.error(e.response.data + "😭", {
position: "top-center",
});
}
};
return (
<Formik
initialValues={{
email: "",
username: "",
password: "",
password2: "",
}}
validationSchema={validationSchema}
onSubmit={submit}
>
{({ values, handleSubmit, handleChange }) => (
<div className="signup-wrapper">
<ToastContainer />
<form onSubmit={handleSubmit} autoComplete="off">
<div className="input-forms">
<div className="input-forms-item">
<div className="input-label">이메일</div>
<TextField
value={values.email}
name="email"
variant="outlined"
onChange={handleChange}
/>
<div className="error-message">
<ErrorMessage name="email" />
</div>
</div>
<div className="input-forms-item">
<div className="input-label">닉네임</div>
<TextField
value={values.username}
name="username"
variant="outlined"
onChange={handleChange}
/>
<div className="error-message">
<ErrorMessage name="username" />
</div>
</div>
<div className="input-forms-item">
<div className="input-label">비밀번호</div>
<TextField
value={values.password}
name="password"
variant="outlined"
type="password"
onChange={handleChange}
/>
<div className="error-message">
<ErrorMessage name="password" />
</div>
</div>
<div className="input-forms-item">
<div className="input-label">비밀번호 확인</div>
<TextField
value={values.password2}
name="password2"
variant="outlined"
type="password"
onChange={handleChange}
/>
<div className="error-message">
<ErrorMessage name="password2" />
</div>
</div>
<Button
color="primary"
variant="contained"
fullWidth
type="submit"
>
회원가입
</Button>
</div>
</form>
</div>
)}
</Formik>
);
};
export default SignUp;
'Frontend > 리액트 개발 꿀팁' 카테고리의 다른 글
ESLint/Prettier 설정 (0) | 2022.06.13 |
---|---|
useState 주의사항 (0) | 2022.06.11 |
상태관리와 리덕스에 대해.. (0) | 2022.06.06 |
formik (0) | 2021.09.08 |
map, key prop (0) | 2021.08.01 |
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!