리액트로 회원가입 폼 구현하기
10월 3일부터 10월 21일까지 3주간 2차 프로젝트가 진행되었다.
공통 주제는 환경 문제였고 우리 팀은 여러 논의 끝에 제로웨이스트 샵을 주제로 정했다. 지도로 사용자 주변에 있는 제로웨이스트 샵을 띄우고 가게에 대한 리뷰 작성과 찜하기 등 여러 기능을 구현하기로 했다.
이 프로젝트에서 내가 맡은 부분은 회원가입 / 로그인 / 간편 로그인 구현
그 중 회원가입부터 정리해보려고 한다. 구현하기에 앞서 조건부터 정리했다.
1-1. 이메일 / 비밀번호 / 닉네임 유효성 검사
const validateEmail = (email) => {
return email
.toLowerCase()
.match(/([\w-.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/);
};
const validatePwd = (password) => {
return password
.toLowerCase()
.match(/^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{10,25}$/);
}
const validateNickname = (nickname) => {
return nickname
.toLowerCase()
.match(/^[ㄱ-ㅎ|가-힣|a-z|A-Z|0-9|].{1,8}$/)
}
1-2. 유효성 검사로 이메일 / 비밀번호 / 닉네임 걸러주기
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPwd, setConfirmPwd] = useState("");
const [nickname, setNickname] = useState("");
const [emailMsg, setEmailMsg] = useState("");
const [pwdMsg, setPwdMsg] = useState('');
const [confirmPwdMsg, setConfirmPwdMsg]= useState("")
const [nicknameMsg, setNicknameMsg] = useState("")
// 1-1에 잡아뒀던 유효성 검사 함수로 정리하기
const isEmailValid = validateEmail(email);
const isPwdValid = validatePwd(password);
const isConfirmPwd = password === confirmPwd;
const isNicknameValid = validateNickname(nickname);
//이메일
const onChangeEmail = useCallback( async (e) => {
const currEmail = e.target.value;
setEmail(currEmail);
if (!validateEmail(currEmail)) {
setEmailMsg("이메일 형식이 올바르지 않습니다.")
} else {
setEmailMsg("올바른 이메일 형식입니다.")
}
})
//비밀번호
const onChangePwd = useCallback((e) =>{
const currPwd = e.target.value;
setPassword(currPwd);
if (!validatePwd(currPwd)) {
setPwdMsg("영문, 숫자, 특수기호 조합으로 10자리 이상 입력해주세요.")
} else {
setPwdMsg("안전한 비밀번호입니다.")
}
}, [])
//비밀번호 확인
const onChangeConfirmPwd = useCallback((e) => {
const currConfirmPwd = e.target.value;
setConfirmPwd(currConfirmPwd);
if (currConfirmPwd !== password) {
setConfirmPwdMsg("비밀번호가 일치하지 않습니다.")
} else {
setConfirmPwdMsg("올바른 비밀번호입니다.")
}
}, [password])
//닉네임
const onChangeNickname = useCallback((e) => {
const currNickname = e.target.value;
setNickname(currNickname);
if (!validateNickname(currNickname)) {
setNicknameMsg("1글자 이상 9글자 미만으로 입력해주세요.")
} else {
setNicknameMsg("올바른 닉네임 형식입니다.")
}
}, []);
// 이런 식으로 return 부분에서 삼항 연산자를 이용해 클래스 네임을 지정해주고
// 클래스네임에 따라 밑에 나오는 문구를 달리 나오게 구현했다.
<ResisterStyled.OutputText className={isEmailValid ? 'success' : 'error'}>
{emailMsg}</ResisterStyled.OutputText> ...(중략)
const [checkMail, setCheckMail] = useState(false)
const [checkNickname, setCheckNickname] = useState(false)
const onCheckEmail = async (e) => {
e.preventDefault();
try {
const res = await Api.post("user/register/email", {email});
const { result } = res.data;
if (!result) {
setEmailMsg("이미 등록된 메일입니다. 다시 입력해주세요.");
setCheckMail(false);
} else {
setEmailMsg("사용 가능한 메일입니다.😊");
setCheckMail(true);
}
} catch (err) {
console.log(err);
}
}
const onCheckNickname = async (e) => {
e.preventDefault();
try {
const res = await Api.post("user/register/nickname", {nickname});
const { result } = res.data;
if (!result) {
setNicknameMsg("이미 등록된 닉네임입니다. 다시 입력해주세요.");
setCheckNickname(false);
} else {
setNicknameMsg("사용 가능한 닉네임입니다.😊");
setCheckNickname(true);
}
} catch (err) {
console.log(err);
}
}
/RegisterForm.jsx
const [isAccepted, setIsAccpted] = useState(false);
const handleCheckAccept = useCallback(() => {
setIsAccpted(true);
}, []);
// 모달창 컴포넌트를 따로 만들어 주었기에 callback props를 사용했다.
/CheckModal.jsx
import React, {useState} from "react";
import * as ModalStyled from "../StyledComponents/ModalStyled";
function CheckModal ( {onCheckAccept, isAccepted, setIsAccpted }) {
const [openModal, setOpenModal] = useState(false);
const showModal = () => {
setOpenModal(true);
}
const closeModal = () => {
setOpenModal(false);
setIsAccpted(false);
}
const onSubmitAccept = () => {
closeModal();
onCheckAccept();
}
return (
<>
<ModalStyled.ModalButton>
<span onClick={showModal} className={isAccepted? 'yes' : 'no'}>약관 동의 *</span>
</ModalStyled.ModalButton>
{openModal ? <ModalStyled.ModalForm>
<h1>정보 이용 약관 동의</h1>
<p>1. 탈퇴 시 회원 정보는 보류 처리되어 14일간 보관됩니다. 이 기간 동안 동일한 메일 주소로 회원가입을 할 시 가입이 제한되며, 탈퇴 취소를 원하시는 경우 취소 처리도 가능합니다.</p>
<p>
2. 탈퇴하더라도 작성하신 리뷰는 계속 남아 있게 됩니다. 작성한 리뷰를 제외한 모든 회원 정보는 삭제되며 남아 있는 리뷰의 작성자명은 '(익명)'으로 처리됩니다. </p>
<p>약관에 동의하십니까? </p>
<button onClick={closeModal} className="left-btn">이전</button>
<button onClick={onSubmitAccept}>동의</button>
</ModalStyled.ModalForm> : null}
</>
)
}
export default CheckModal;
가입 폼과 마찬가지로 약관 동의를 하면 isAccepted가 true가 되어 className이 'yes'가 되고 아니면 'no'여서 글자 색깔이 동의 여부에 따라 변하게 구현했다.
// 앞에 정리한 유효성 검사를 한번에 묶어주고
const isAllValid = isEmailValid && isPwdValid && isConfirmPwd && isNicknameValid && isAccepted && checkMail && checkNickname;
// return 부분에서 disabled 값으로 제어해주었다.
<ResisterStyled.FootBtnBox>
<ResisterStyled.FootButton onClick={onSubmit} type="submit" disabled={!isAllValid}>
가입하기
</ResisterStyled.FootButton>
</ResisterStyled.FootBtnBox>
아쉬운 점
이메일 인증
진짜 존재하는 이메일인지 이메일 인증을 구현하자고 초반에 이야기가 나왔는데 시간이 없어 방안만 생각하고 직접 코드로 짜지 못했다. 가입 절차 중 사용자가 이메일 인증 버튼을 누르거나 회원가입 절차가 전부 끝났을 때 입력한 주소로 인증 메일이 갔다고 알린다 -> 메일을 눌러 보면 안에 우리 홈페이지로 돌아오는 링크가 걸려 있어 링크를 누르면 인증이 완료되는 식으로 구현해보고 싶었다.
그런데 의문 🤔
유효성 검사와 중복 확인, 약관 동의까지 전부 거쳤는데 또 이메일 인증을 하라고 하면 사용자 입장에서 너무 귀찮을 것 같다 . . . 구현할 시간이 있었다면 이건 또 이것대로 팀원들과 충분한 논의를 거쳐야 할 문제로 보인다.
중복일 때 뜨는 메시지 색깔
메일이든 닉네임이든 중복일 때 이미 등록된 ~라고 밑에 메시지를 띄워 준다. 색깔은 초록색이다.
(1) isEmailValid에 조건을 하나 더 걸자니 중복 확인을 하고 다시 메일을 칠 때 나오는 '올바른 이메일 형식입니다.' 문구가 빨간색이다.
(2) 삼항 연산자를 하나 더 걸어 색을 구분하려 했더니 여러 조건으로 테스트를 해봤을 때 앞 클래스에 따라 뒤 클래스 색이 뒤바뀌어 나오는 등의 문제가 생겼다.
결국 중복 확인 메시지는 초록색으로 띄워주게 되었다. 중복 확인 로직을 까먹고 있다가 중간에 투입한 거라 이런 문제가 생긴 걸지도 모르겠다. 다음에는 로직을 처음부터 제대로 짜서 이런 문제가 없도록 해봐야겠다.
className={` ${isEmailValid ? 'success' : 'error'} ${checkMail? 'checked' : 'not-checked'}`}
(😢혹시 해결 방안을 아시는 분이 계시다면 댓글 부탁드립니다. 감사합니다.😢)
배운 점