나는 요즘 React로 REST API를 가지고 쇼핑몰 기능을 조금씩 구현하고 있다. 멋쟁이 사자처럼 팀플에서는 바닐라자바스크립트를 뿌셨기(부서지는 건 역시 나였지만) 때문에, React 혼자 공부하며 차근차근 만들어 가게 되었다. 왜냐하면 팀플이다보니, 나는 post 요청이나 로그인, 회원가입, 토큰 인증 등 FE에서 핵심이라고 할 수 있 기능들을 해보지 못 한 채 부트 캠프가 끝나버렸다.
로그인은 onChange로 input 값을 추적하는 것만 해주면 큰 어려움이 없었다. (혼자 만족, 이때부터 회원가입도 금방 해버릴 수 있을 거라는 깊은 착각과 오만의 늪에 빠지기 시작함) .
회원가입 UI를 다 만들고 어떤 것부터 해야할지 몰랐다. 일단 로그인 기능구현처럼 input 값들을 하나씩 useState에 담기 시작했다. 그리고 post 요청을 냅다 했더니 성공! 하지만, 예외처리랑 error 메세지를 어떻게 해야 하는지 몰랐다. 정말 많은 블로그 글들과 강의들을 찾아보면서 알게된 것 바로 Form Hook 라이브러
하지만, React 회원가입 기능 동작을 기초부터 알아가고 싶었고 공부하는 단계이기 때문에 hook form을 사용을 하지 않고 눈물을 흘리며 하나씩 코드를 짰다.
순서)
1. useState에 input 값을 담고 onChagne 이벤트 핸들러를 이용해서 실시간 추적
2. useState로 Error 메세지와 예외 처리에 사용할 true, false 상태 담기
3. 예외 처리(정규식 사용)
4. error 메세지 생성
const signUp = async () => {
try {
const response = await AxiosInstance.post("accounts/signup/", {
username: username, // 아이디
password: password1,
password2: password2,
phone_number: phoneNum,
name: name, // 이름
});
console.log(response);
if (response.status === 201) {
successJoin();
}
} catch {
console.error("ERROR");
}
};
const navigate = useNavigate();
const successJoin = () => {
navigate("/login");
};
// 가입하기 버튼 누르기
const handleSubmitJoin = (e) => {
e.preventDefault();
signUp();
};
console.log(username, password1, password2, name, phoneNum);
// input값 입력 & 유효성 검사
//아이디
// 20자 이내의 영어 소문자,대문자, 숫자만 가능
const handleUsername = useCallback((e) => {
const patternUsername = /^[a-zA-Z0-9]{1,20}$/;
const usernameCurrent = e.target.value;
setUsername(usernameCurrent);
if (!patternUsername.test(usernameCurrent)) {
setUsernameMsg("20자 이내의 영어 소문자,대문자, 숫자만 가능합니다.");
setIsUsername(false);
} else {
setUsernameMsg("멋진 아이디네요 :)");
setIsUsername(true);
}
}, []);
// 비밀번호
// 비번 10자리 이상, 영소문자 포함,
const handlePw1 = useCallback((e) => {
const patternPw1 = /^[a-zA-Z0-9!@#$%^&*]{10,50}$/;
const password1Current = e.target.value;
setPassword1(password1Current);
if (!patternPw1.test(password1Current)) {
setPassword1Msg("영소문자, 숫자를 포함한 10자 이상이어야 합니다.");
setIsPassword1(false);
} else {
setPassword1Msg("안전한 비밀번호에요 :)");
setIsPassword1(true);
}
}, []);
// 비밀번호 확인
const handlePw2 = useCallback(
(e) => {
const password2Current = e.target.value;
setPassword2(password2Current);
if (password1 === password2Current) {
setPassword2Msg("비밀번호가 일치합니다 :)");
setIsPassword2(true);
} else {
setPassword2Msg("비밀번호와 일치하지 않습니다. 다시 입력해주세요.");
setIsPassword2(false);
}
},
[password1]
);
//이름
const handleName = (e) => {
setName(e.target.value);
setNameMsg("멋진 이름이네요 :)");
setIsName(true);
};
//휴대전화
//핸드폰 번호는 010으로 시작하는 10-11자리 숫자
//^010([0-9]{3,4})([0-9]{4})$
const handlephoneNum = useCallback((e) => {
const patternPhoneNum = /^010([0-9]{3,4})([0-9]{4})$/;
const phoneNumCurrent = e.target.value;
setPhoneNum(phoneNumCurrent);
setIsPhoneNum(true);
if (!patternPhoneNum.test(phoneNumCurrent)) {
setPhoneNumMsg("- 없이 010으로 시작하는 10-11자리 숫자를 입력하세요");
setIsPhoneNum(false);
} else {
setPhoneNumMsg("올바른 형식입니다 :)");
setIsPhoneNum(true);
}
}, []);
return (
<LoginSection>
<h2 className="ir">로그인 페이지</h2>
<MainLogo src={mainLogo} alt="메인로고" />
<LoginDiv>
<form onSubmit={handleSubmitJoin}>
<JoinLabel htmlFor="username">
아이디
<FlexDiv1>
<JoinIdInput
id="username"
name="username"
type="text"
required
onChange={handleUsername}
/>
<CheckOverlapBtn>중복확인</CheckOverlapBtn>
</FlexDiv1>
{(username.length > 0 && !isUsername && (
<ErrorMsg>{usernameMsg}</ErrorMsg>
)) ||
(username.length > 0 && isUsername && (
<SuccessMsg>{usernameMsg}</SuccessMsg>
))}
</JoinLabel>
<JoinLabel htmlFor="password">
비밀번호
<JoinInput
id="password"
name="password"
type="password"
onChange={handlePw1}
required
/>
{(password1.length > 0 && !isPassword1 && (
<ErrorMsg>{password1Msg}</ErrorMsg>
)) ||
(password1.length > 0 && isPassword1 && (
<SuccessMsg>{password1Msg}</SuccessMsg>
))}
</JoinLabel>
<JoinLabel htmlFor="checkPW">
비밀번호 재확인
<JoinInput
id="checkPW"
name="password"
type="password"
onChange={handlePw2}
required
/>
{(password2.length > 0 && !isPassword2 && (
<ErrorMsg>{password2Msg}</ErrorMsg>
)) ||
(password2.length > 0 && isPassword2 && (
<SuccessMsg>{password2Msg}</SuccessMsg>
))}
</JoinLabel>
<JoinLabel htmlFor="name">
이름
<JoinInput
id="name"
name="name"
type="text"
onChange={handleName}
required
/>
{(name.length > 0 && !isName && <ErrorMsg>{nameMsg}</ErrorMsg>) ||
(name.length > 0 && isName && <SuccessMsg>{nameMsg}</SuccessMsg>)}
</JoinLabel>
<JoinLabel htmlFor="number">
휴대폰번호
<JoinInput
id="phoneNum"
name="phoneNum"
type="text"
onChange={handlephoneNum}
required
/>
{(phoneNum.length > 0 && !isPhoneNum && (
<ErrorMsg>{phoneNumMsg}</ErrorMsg>
)) ||
(phoneNum.length > 0 && isPhoneNum && (
<SuccessMsg>{phoneNumMsg}</SuccessMsg>
))}
</JoinLabel>
<JoinLabel htmlFor="이메일">
이메일
<JoinInput id="email" name="email" type="email" />
</JoinLabel>
</form>
</LoginDiv>
<form onSubmit={handleSubmitJoin}>
<CheckBoxP>
<input type="checkbox" required />
호두샵의 <a href="#none">이용약관</a> 및{" "}
<a href="#none">개인정보처리방침</a>에 대한 내용을 확인하였고
동의합니다.
</CheckBoxP>
<LoginBtn>가입하기</LoginBtn>
</form>
</LoginSection>
);
};
🔗 참고 강의: https://www.youtube.com/watch?v=tWOn7g_3wKU
watch
와 {...required('name' ,{예외처리})}
을 사용하면 input 값 추적이 너무 손쉽다. 폼 hook을 쓰지 않으면 useState와 onChange를 사용해야 하는데 코드가 훨씬 간결하다. {...required('name' ,{예외처리})}
에 name을 명시하면 굳이 따로 id, name 등을 쓰지 않아도 된다. 한마디로 하나로 통일에서 사용 가능하다. import React, { useRef } from "react";
import { useForm } from "react-hook-form";
import "../src/app.css";
export default function App() {
const {
register,
watch,
formState: { errors },
handleSubmit,
} = useForm();
console.log(watch("email")); // email input 값 추적
console.log(watch("name")); // name input 값 추적
console.log(watch("password")); // password input 값 추적
console.log(watch("password_confirm")); // password_confirm input 값 추적
//password_confrim
const password = useRef(); //dom을 선택할 수 있게 ref 생성
password.current = watch("password"); // password 필드 값 가져오기
// event대신 data
const onSubmit = (data) => {
console.log(data, "data");
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>Email</label>
<input
type="email"
{...register("email", { required: true, pattern: /^\S+@\S+$/i })}
/>
{errors.email && <p>이메일 입력은 필수입니다</p>}
<label>Name</label>
<input {...register("name", { required: true, maxLength: 8 })} />
{errors.name && errors.name.type === "required" && (
<p>이름은 입력은 필수입니다</p>
)}
{errors.name && errors.name.type === "maxLength" && (
<p>최대 8글자까지 입력이 가능합니다.</p>
)}
<label>Password</label>
<input
type="password"
{...register("password", { required: true, minLength: 6 })}
/>
{errors.password && errors.password.type === "required" && (
<p>비밀번호는 입력은 필수입니다</p>
)}
{errors.password && errors.password.type === "minLength" && (
<p>최소 6글자 이상 입력해야 합니다.</p>
)}
<label>Password Confirm</label>
<input
type="password"
{...register("password_confirm", {
required: true,
validate: (value) => value === password.current,
})}
/>
{errors.password_confirm &&
errors.password_confirm.type === "required" && (
<p>비밀번호는 재확인은 필수입니다</p>
)}
{errors.password_confirm &&
errors.password_confirm.type === "validate" && (
<p>비밀번호가 일치하지 않습니다</p>
)}
<input type="submit" />
</form>
);
}