일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- stock option
- 회원가입로직
- ui탬플릿
- 스프링시큐리티
- MRC
- jsonwebtoken
- Typesciprt
- 관리자페이지
- react
- Token
- 로그인
- 배포
- 이미지 업로드
- 마이페이지
- Styled Components
- register
- 빌드 및 배포
- RCPS
- 로그인 로직
- Update
- 캘린더 라이브러리
- 인증처리
- 밸류즈
- 밸류즈 홈페이지
- 달력 라이브러리
- Ajax
- 파생상품평가
- userManagement
- mypage
- 공통메서드
- Today
- Total
I T H
[MySchedule project] 4. frontend - 회원가입 로직 본문
frontend의 회원가입의 흐름과, 어떤함수들을 사용하고, 타입스크립트를 어떻게 적용하는지에 대해 작성할 것임.
들어가기에 앞서 이번챕터의 사용할 훅들과 함수들을 미리 알아보자!
- useForm: form을 관리하는 커스텀훅으로 state와 onChange함수의 많은 양을 줄이고 반복적인 코드의 양을 줄일 수 있다. 회원가입과 같이 입력받을 컬럼들이 많을 경우 사용하면 유용할 훅이라고 볼수있다.
useFrom은 여러가지의 리턴 props을 가지고 있다. 필자는 register, handleSubmit, reset, watch 등을 사용했다.
- 입력받을 변수 타입들에 대한 ts파일을 만들어서 import해서 사용함.
- 지난 챕터에서 미리 만들어둔 useAddDispatch를 사용해 리듀서를 호출할 것이다.
[proxy 설정]
- 먼저 package.json에 백앤드 url을 호출하는 proxy를 넣어주자.

[src / types폴더 / register.ts]
- useForm에 넣을 타입과 onSubmit함수 호출시 전달할 파라미터의 타입에 대해 interface로 만들어놨다.
export interface RegisterValue {
userId: string;
userPwd: string;
userPwdCheck: string;
userName: string;
userPhone: string;
userEmail: string;
}
export interface RegisterParams {
userId: string;
userPassword: string;
userName: string;
userPhone: string;
userEmail: string;
userImage: string;
}
[src / pages / Register폴더 / index.tsx]
1) useForm 사용
- register: validation check(유효성검사)를 하였음.
이메일양식 체크와 같은 패턴과 자리수 체크와 같은 기능을 넣을수있다.
- watch : input태그의 값들을 가져올수있다. useRef함수와 같이 사용하였음.
- reset: 값들을 초기화 한다.
- handleSubmit: form 안의 데이터들을 받는 함수이다. from태그의 onsubmit안에 지정된 함수로 이동함.
- mode는 onChange를 사용해 인풋 태그의 값을 입력받을때마다 validation check를 하게 하였음.
- 타입스크립트 사용시 useForm의 타입을 넣어주어야함. 위에서 미리 만들어논 타입을 import함. java의 제네릭문법임.
useForm<RegisterValue>({ mode: "onChange" });
2) Email 자동완성 함수
- email의 @까지 입력하면 @뒤에 도메인주소가 자동으로 뜨게 설정하였음.
사용자가 쉽게 이메일주소를 입력할수있도록 구현함.
3) id 중복확인 함수
- 똑같은 id가 db에 있는지 체크하는 함수임.
- 이함수는 단순히 체크만 하면되서 리덕스의 dispatch를 사용하진 않고, 바로 axios를 사용해 백앤드로 id값을 보냄.
- id값이 있다면 toast로 창을 띄운 후 값을 재입력 받게 하였고, 없다면 사용가능한 아이디로 중복확인버튼을 사라지게 하였음.
4) onSubmit 함수
- dispatch함수를 사용해 리덕스의 thunkFunction.tsx로 전달하였다. 타입을 지정해놔야하므로, 미리 등록해둔 타입을 지정한 메서드를 가져와서 사용하였음. ( redux.ts에 만들어놓은 useAddDispatch )
- 회원가입이 완료되었다면 (백앤드의 데이터에 잘 저장되었다면) reset()으로 초기화시키고, navigate함수로 메인페이지로 이동하게 하였음.
- 전달하는 파라미터의 타입과 받는쪽의 타입을 잘 넣도록 하자!
import React, { ChangeEvent, useRef, useState } from "react";
import { RegisterParams, RegisterValue } from "../../types/register";
import { useForm } from "react-hook-form";
import axiosInstance from "../../utils/axios";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { userRegister } from "../../store/thunkFunction";
import { useNavigate } from "react-router-dom";
import { useAddDispatch } from "../../store/redux";
const Register = () => {
const {
register, //등록함수
handleSubmit,
formState: { errors },
reset,
watch,
} = useForm<RegisterValue>({ mode: "onChange" });
//} = useForm<RegisterValue>({ mode: "onSubmit" });
const dispatch = useAddDispatch();
const navigate = useNavigate();
const [userEmail, setUserEmail] = useState("");
const [emailList, setEmailList] = useState<string[]>([]);
const [idcheck, setIdCheck] = useState(false);
const domainEmails = [
"@naver.com",
"@gmail.com",
"@daum.net",
"@nate.com",
"@kakao.com",
];
const emailForm =
/^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
//이메일 자동완성 함수
const emailHandleChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
const userEmails = domainEmails.map((email) =>
e.target.value.includes("@")
? e.target.value.split("@")[0] + email
: e.target.value + email
);
setUserEmail(e.target.value);
setEmailList(userEmails);
};
// VALIDATION CHECK START
// register등록함수와 함께 사용한다. {...register("userId", userId)}
const userId = {
required: "필수입력 요소입니다.",
minLength: {
value: 3,
message: "최소 3글자 이상으로 입력해 주세요.",
},
};
const userPwd = {
required: "필수입력 요소입니다.",
pattern: {
value:
/^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{11,20}$/,
message: "영문, 숫자, 특수문자 포함 11자 이상 20 자 이하로 입력해 주세요",
},
};
const userName = {
required: "필수입력 요소입니다.",
};
const userPhone = {
required: "필수입력 요소입니다.",
minLength: {
value: 11,
message: "숫자 11자리 이상으로 입력해 주세요.",
},
};
//비밀번호 일치여부 체크 - watch사용해서 input의 userPwd의 값을 가져옴
const passwordRef = useRef<string | null>(null);
passwordRef.current = watch("userPwd");
//VALIDATION CHECK END
//아이디 체크
const handleIdCheck = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault();
let id = watch("userId");
if (id === "" || id === null) {
toast.info("아이디를 입력해주세요.");
return;
}
const body = {
userId: id,
};
try {
const idCheckValue = await axiosInstance.post("/register/idCheck", body);
console.log(idCheckValue);
if (idCheckValue.data !== "") {
//값이 있으면
toast.info("이미 등록된 아이디 입니다.");
} else {
toast.info("사용가능한 아이디 입니다.");
setIdCheck(true);
}
} catch (error) {
console.error(error);
}
};
//Submit 버튼 이벤트
const onSubmit = (
// event: React.FormEvent<HTMLFormElement>,
{ userId, userPwd, userName, userPhone }: RegisterValue
) => {
// event.preventDefault();
const body: RegisterParams = {
userId,
userName,
userPhone,
userEmail,
userPassword: userPwd,
userImage: "",
};
console.log(body);
if (idcheck) {
dispatch(userRegister(body));
reset();
navigate("/");
} else {
toast.info("아이디 중복확인을 먼저 해주세요.");
}
};
return (
<section className="sm:max-w-[900px] max-w-[1100px] m-auto mt-16">
<h2 className="text-2xl font-bold text-center my-3">회원가입</h2>
{/* <hr className="h-3 my-2" /> */}
{/* <div className="p-3 bg-white rounded-md shadow-md"> */}
<div className="p-3 bg-white rounded-md my-2">
<form className="w-full" onSubmit={handleSubmit(onSubmit)}>
<table className="sm:w-8/12 w-10/12 m-auto text-left ">
<thead></thead>
<tbody>
<tr className="border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[11px] font-semibold sm:w-4/12 w-5/12">
아이디
</th>
<td className="py-3 w-8/12">
<input
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
type="text"
id="userId"
disabled={idcheck}
{...register("userId", userId)}
/>
{idcheck ? (
<button disabled={idcheck}></button>
) : (
<button
onClick={handleIdCheck}
className="sm:w-15 w-15 mx-2 m-auto bg-blue-500 h-[45px] text-white font-semibold sm:text-[14px] text-xs py-2 px-2 rounded-md hover:bg-blue-600"
>
중복확인
</button>
)}
{errors?.userId && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
{errors.userId.message}
</span>
</div>
)}
</td>
</tr>
<tr className=" border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[11px] font-semibold ">
비밀번호
</th>
<td className="py-3 w-8/12">
<input
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
type="password"
id="userPwd"
{...register("userPwd", userPwd)}
/>
{errors?.userPwd && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
{errors.userPwd.message}
</span>
</div>
)}
</td>
</tr>
<tr className=" border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[12px] font-semibold">
비밀번호확인
</th>
<td className="py-3 w-8/12">
<input
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
type="password"
id="userPwdCheck"
minLength={11}
{...register("userPwdCheck", {
required: "필수입력 요소입니다.",
validate: (value) => value === passwordRef.current,
})}
/>
{errors?.userPwdCheck?.type === "required" && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
{errors.userPwdCheck.message}
</span>
</div>
)}
{errors?.userPwdCheck?.type === "validate" && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
비밀번호가 맞지 않습니다. 한번더 확인해 주세요.
</span>
</div>
)}
</td>
</tr>
<tr className=" border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[11px] font-semibold ">
사용자명
</th>
<td className="py-3 w-8/12">
<input
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
type="text"
id="userName"
{...register("userName", userName)}
/>
{errors?.userName && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
{errors.userName.message}
</span>
</div>
)}
</td>
</tr>
<tr className=" border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[11px] font-semibold ">
휴대폰번호
</th>
<td className="py-3 w-8/12">
<input
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
type="number"
id="userPhone"
{...register("userPhone", userPhone)}
/>
{errors?.userPhone && (
<div className="py-1">
<span className="text-red-500 pl-2 text-sm">
{errors.userPhone.message}
</span>
</div>
)}
</td>
</tr>
<tr className="border-t-[1px] h-[60px] w-full">
<th className="p-3 bg-neutral-100 sm:text-[14px] text-[11px] font-semibold ">
이메일
</th>
<td className="py-3 w-8/12">
<input
id="userEmail"
className="my-2 mx-2 sm:w-[250px] w-[150px] rounded-md border-[1px] border-gray-200 border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
list="email"
value={userEmail}
onChange={emailHandleChange}
/>
<datalist id="email">
{emailList &&
emailList.map((email, idx) => (
<option value={email} key={idx} />
))}
</datalist>
{userEmail && !emailForm.test(userEmail) && (
<p className="p-2 text-red-500 text-sm">
이메일 형식을 확인해주세요.
</p>
)}
</td>
</tr>
<tr className="border-t-[1px] h-[60px] w-full">
<td colSpan={2} className="text-center pl-0">
<button
type="submit"
className="m-auto w-2/12 bg-blue-500 h-[45px] text-white font-semibold sm:text-[16px] text-sm py-2 my-4 px-2 rounded-md hover:bg-blue-600"
>
가입
</button>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</section>
);
};
export default Register;
[ src / store / thunkFunction.tsx ]
- 파마리터(body) 타입 : ' types폴더 / register.ts' 에서 지정한 타입을 넣어주었음.
- request url : 'http://localhost:OOOO/register', parameter: body에 id값을 넣어서 백앤드로 호출하였고,
백앤드에서 전달받은 값을 받아 response에 넣었다.
return response.data는 redux의 payload임. 'store / userSlice.tsx'의 reducer에 payload로 넣으면 store에 저장된다.
import { createAsyncThunk } from "@reduxjs/toolkit";
import axiosInstance from "../utils/axios";
import { RegisterParams } from "../types/register";
export const userRegister = createAsyncThunk(
"userRegister",
async (body: RegisterParams, thunkAPI) => {
try {
const response = await axiosInstance.post(`/register`, body);
return response.data; //payload
} catch (error: any) {
console.log(error);
return thunkAPI.rejectWithValue(error.response.data || error.message);
}
}
);
[ src / store / userSlice.tsx ]
- return response.data 는 payload이다. 백앤드에서 받아오는 데이터가 있을 경우엔 action.payload 데이터를 미리 만들어둔 (initialState) -> state에 넣어준다. 그렇게되면 redux - store에 저장된 데이터가 뜨게 됨.
const userSlice = createSlice({
name: "user",
initialState: initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(userRegister.pending, (state: any) => {
state.isLoading = true;
})
.addCase(userRegister.fulfilled, (state) => {
state.isLoading = false;
toast.info("회원가입이 완료되었습니다.");
})
.addCase(userRegister.rejected, (state, action: any) => {
state.isLoading = false;
state.error = action.payload;
toast.error(action.payload);
});
},
});
[결과화면]
- 회원가입 / id 중복확인


- 회원가입 / 이메일 자동완성기능


'React + Node.js' 카테고리의 다른 글
[MySchedule project] 6. JWT(json web token) / 로그인 로직(1) (2) | 2024.01.30 |
---|---|
[MySchedule project] 5. backend - 회원가입 로직 (0) | 2024.01.25 |
[MySchedule project] 3. frontend - redux 세팅(redux-toolkit사용) + typescript (0) | 2024.01.25 |
[MySchedule project] 2. frontend - 레이아웃 / react- router 설정 (1) | 2024.01.25 |
[MySchedule project] 1. frontend - 타입스크립트 초기 환경설정 (0) | 2024.01.25 |