728x90
✨회원가입
- react-hook-form & yup으로 폼 제출하기
👇🏻기본 틀
const {
register,
handleSubmit,
formState: { errors, isValid },
trigger,
} = useForm<FormData>({
resolver: yupResolver(authSchema),
mode: 'all',
});
// confirmPassword를 제외하고 폼 제출
const onSubmit: SubmitHandler<FormData> = ({ confirmPassword, ...data }) => {
console.log('폼 제출', data );
};
return (
<div className="page-container">
<div className="flex flex-col items-center justify-center">
<Link href="/" aria-label="메인페이지로 이동">
<Image
src="/logo/tripzip.png"
alt="trip.zip"
width={300}
height={20}
/>
</Link>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-full max-w-640 flex-col gap-16"
>
<Input
label="이메일"
name="email"
type="text"
placeholder="이메일을 입력해 주세요"
register={register('email')}
error={errors.email}
onBlur={() => trigger('email')}
/>
<Input
label="닉네임"
name="nickname"
type="text"
placeholder="닉네임을 입력해 주세요"
register={register('nickname')}
error={errors.nickname}
onBlur={() => trigger('nickname')}
/>
<Input
label="비밀번호"
name="password"
type="password"
placeholder="8자 이상 입력해 주세요"
register={register('password')}
error={errors.password}
onBlur={() => trigger('password')}
/>
<Input
label="비밀번호 확인"
name="confirmPassword"
type="password"
placeholder="비밀번호를 한번 더 입력해 주세요"
register={register('confirmPassword')}
error={errors.confirmPassword}
onBlur={() => trigger('confirmPassword')}
/>
<Button
type="submit"
className="rounded-md"
variant={isValid ? 'activeButton' : 'disabledButton'}
>
회원가입 하기
</Button>
</form>
<div className="text-md mt-20 flex gap-8">
<p>회원이신가요?</p>
<Link
href="login"
className="text-custom-green-200 underline decoration-custom-green-200 underline-offset-2"
>
로그인하기
</Link>
</div>
</div>
</div>
);
const onSubmit: SubmitHandler<FormData> = ({ confirmPassword, ...data }) => {
console.log('폼 제출', data );
};
- input 컴포넌트 프롭으로 register가 필수이기 때문에 어쩔 수 없이 confirmPassword에 register에 연결되었다…
- 폼을 제출할 때 confirmPassword는 필요없어서 구조 분해 할당을 통해 confirmPassword를 제외한 나머지 필드를 data 객체로 모은다!!
[🔥React 뽀개기] - input 공통 컴포넌트로 만들기 (feat. react-hook-form + yup)
🐥react-query를 사용해 api post요청하기
react query type지정하는 방법 👇🏻
https://growing-jiwoo.tistory.com/118
const mutation = useMutation<RegisterResponse, Error, RegisterRequest>(
postUser,
{
onSuccess: (data: RegisterResponse) => {
console.log('회원가입 성공', data);
// TODO: 모달 띄우기
},
onError: (error: Error) => {
console.error('회원가입 실패', error);
},
},
);
// confirmPassword를 제외하고 폼 제출
const onSubmit: SubmitHandler<FormData> = ({ confirmPassword, ...data }) => {
const registerData: RegisterRequest = {
email: data.email,
nickname: data.nickname,
password: data.password,
};
console.log('폼 제출', registerData);
mutation.mutate(registerData);
};
💣트러블 슈팅
'(userData: RegisterRequest) => Promise<RegisterResponse>' 유형에 'UseMutationOptions<RegisterResponse, Error, RegisterRequest, unknown>' 유형과 공통적인 속성이 없습니다.ts(2559)
자꾸 postUser에 빨간 줄 뜸,,,!!!
https://beomy.github.io/tech/react/tanstack-query-v4/
const {
register,
handleSubmit,
formState: { errors, isValid },
trigger,
} = useForm<FormData>({
resolver: yupResolver(authSchema),
mode: 'all',
});
const mutation = useMutation<RegisterResponse, Error, RegisterRequest>({
mutationFn: postUser,
onSuccess: (data: RegisterResponse) => {
console.log('회원가입 성공', data);
// TODO: 모달 띄우기
},
onError: (error: Error) => {
console.error('회원가입 실패', error);
},
});
- mutationFn에다가 함수 써줘야함…
✨로그인
import tripZip from '@/../public/logo/tripZip.png';
import Button from '@/components/button';
import Input from '@/components/input/Input';
import { postLogin } from '@/libs/api/auth';
import { authSchema } from '@/libs/utils/authSchema';
import { yupResolver } from '@hookform/resolvers/yup';
import { useMutation } from '@tanstack/react-query';
import { LoginResponse } from '@trip.zip-api';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import React from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
type FormData = {
email: string;
password: string;
};
export default function Signup() {
const {
register,
handleSubmit,
formState: { errors, isValid },
trigger,
} = useForm<FormData>({
resolver: yupResolver(authSchema),
mode: 'all',
});
const router = useRouter();
const mutation = useMutation<LoginResponse, Error, FormData>({
mutationFn: postLogin,
onSuccess: (data: LoginResponse) => {
console.log('로그인 성공', data);
// TODO: 모달 띄우기
router.push('/');
},
onError: (error: Error) => {
if (error.message === '존재하지 않는 유저입니다.') {
alert('존재하지 않는 유저입니다.');
// TODO: alert 대신 모달 띄우기
} else if (error.message === '비밀번호가 일치하지 않습니다.') {
alert('비밀번호가 일치하지 않습니다.');
} else {
console.error('로그인 실패', error);
}
},
});
const onSubmit: SubmitHandler<FormData> = (data) => {
mutation.mutate(data);
};
return (
<div className="page-container">
<div className="flex flex-col items-center justify-center">
<Link href="/" aria-label="메인페이지로 이동">
<Image src={tripZip} alt="trip.zip" width={300} height={20} />
</Link>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-full max-w-640 flex-col gap-16"
>
<Input
label="이메일"
name="email"
type="text"
placeholder="이메일을 입력해 주세요"
register={register('email')}
error={errors.email}
onBlur={() => trigger('email')}
/>
<Input
label="비밀번호"
name="password"
type="password"
placeholder="비밀번호를 입력해 주세요"
register={register('password')}
error={errors.password}
onBlur={() => trigger('password')}
/>
<Button
type="submit"
className="rounded-md"
variant={isValid ? 'activeButton' : 'disabledButton'}
>
로그인 하기
</Button>
</form>
<div className="text-md mt-20 flex gap-8">
<p>회원이 아니신가요?</p>
<Link
href="login"
className="text-custom-green-200 underline decoration-custom-green-200 underline-offset-2"
>
회원가입하기
</Link>
</div>
</div>
</div>
);
}
const mutation = useMutation<LoginResponse, Error, FormData>({
mutationFn: postLogin,
onSuccess: (data: LoginResponse) => {
console.log('로그인 성공', data);
// TODO: 모달 띄우기
router.push('/');
},
onError: (error: Error) => {
if (error.message === '존재하지 않는 유저입니다.') {
alert('존재하지 않는 유저입니다.');
// TODO: alert 대신 모달 띄우기
} else if (error.message === '비밀번호가 일치하지 않습니다.') {
alert('비밀번호가 일치하지 않습니다.');
} else {
console.error('로그인 실패', error);
}
},
});
- error메세지 중에 비밀번호가 일치하지 않으면 모달 띄우기(요구사항임)
🍪로그인 시 쿠키로 정보 저장하기
쿠키 설정을 구현해서 하는 방법
document.cookie = `accessToken=${data.accessToken}; path=/; secure; samesite=strict`;
document.cookie = `refreshToken=${data.refreshToken}; path=/; secure; samesite=strict`;
- 쿠키에 accessToken, refreshToken 저장하기
- / 경로면 쿠키에 접근할 수 있도록
- secure: HTTPS 연결에서만 전송되도록
- samesite=strict: CSRF(교차 사이트 요청 위조) 공격을 방지
// 쿠키 설정 함수
export function setCookie(name: string, value: string, days: number) {
if (typeof document !== 'undefined') {
const expires = days
? `; expires=${new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString()}`
: '';
document.cookie = `${name}=${value}${expires}; path=/`;
}
}
// 쿠키 삭제 함수
export function deleteCookie(name: string) {
if (typeof document !== 'undefined') {
document.cookie = `${name}=; Max-Age=0; path=/`;
}
}
// 쿠키 읽기 함수
export function getCookie(name: string): string | null {
if (typeof document !== 'undefined') {
const nameEQ = `${name}=`;
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
if (cookie.startsWith(nameEQ)) {
return cookie.substring(nameEQ.length);
}
}
}
return null;
}
// 로그인 상태 확인 함수
export function isLogin(): boolean {
const accessToken = getCookie('accessToken');
return accessToken !== null;
}
const logout = () => {
deleteCookie('accessToken');
deleteCookie('refreshToken');
deleteCookie('isLogin');
router.push('/');
};
- 호출해서 쓰기
- 로그인시 쿠키에 accessToken과 refreshToken이 저장되는 것을 볼 수 있다.
'🔥React 뽀개기' 카테고리의 다른 글
path를 따라가는 로딩화면 구현하기 (0) | 2024.08.16 |
---|---|
Toast 공통 컴포넌트 만들기 (feat.react-toastify) (0) | 2024.08.08 |
input 공통 컴포넌트로 만들기 (feat. react-hook-form + yup) (0) | 2024.08.01 |
라이브러리 없이 state로 폼 만들기 (0) | 2024.07.31 |
스크롤시 투명한 배경에서 배경색 넣기(feat. 화살표 클릭 시 맨 위로 이동하기) (0) | 2024.07.27 |