728x90
✅Zod, React Hook Form으로 회원가입 만들기
❓Zod
기존에 yup을 사용하다가 zod도 많이 쓰는 걸 봐서 사용해봤다. zod는 타입스크립트와의 호환성이 뛰어나다. zod는 타입 추론을 자동으로 지원하므로, 별도의 타입을 명시하지 않고도 스키마에서 바로 타입을 추론할 수 있다.
type FormData = z.infer<typeof signupSchema>; // signupSchema로부터 타입 자동 추론
0. zod 설치
npm install react-hook-form zod @hookform/resolvers
1. 회원가입 스키마 만들기
zodResolver를 이용해 zod 스키마로 유효성 검사를 해준다.
import { z } from 'zod'
export type SignupInput = z.infer<typeof signupSchema>
export const signupSchema = z
.object({
id: z.string().min(1, '아이디는 필수입니다.'),
nickname: z
.string()
.max(10, '열 자 이하로 작성해주세요.')
.min(1, '닉네임은 필수입니다.'),
password: z
.string()
.min(8, '8자 이상 입력해주세요.')
.regex(/[a-z]/, '소문자가 포함되어야 합니다.')
.regex(/[0-9]/, '숫자가 포함되어야 합니다.')
.regex(/[!@#$%^&*(),.?":{}|<>]/, '특수문자가 포함되어야 합니다.')
.regex(/^\S*$/, '공백을 포함할 수 없습니다.')
.min(1, '비밀번호는 필수입니다.'),
confirmPassword: z.string().min(1, '비밀번호 확인은 필수입니다.'),
})
.refine((data) => data.password === data.confirmPassword, {
path: ['confirmPassword'],
message: '비밀번호가 일치하지 않습니다.',
})
⚠️nonempty
ZodString 타입에서 nonempty 메서드를 사용할 수 없다는 오류 메시지다.
nonempty대신 min(1)을 사용하여 빈 값을 허용하지 않도록 설정할 수 있다!!
📍refine()
zod에서 .refine()는 조건을 정의하고, 그 조건이 충족되지 않으면 유효성 검사를 실패하도록 만든다.
이 메서드는 두 가지 인자를 받는다.
- 첫 번째 인자: 조건을 나타내는 함수
- 두 번째 인자: 오류 메시지 및 해당 조건에 관련된 세부 설정을 담은 객체
.refine((data) => data.password === data.confirmPassword, {
path: ['confirmPassword'],
message: '비밀번호가 일치하지 않습니다.',
})
첫 번째 인자 ((data) => data.password === data.confirmPassword)
- 이 부분은 refine 메서드가 확인할 조건을 정의한다.
- data는 zod 스키마에 의해 검사된 폼 데이터를 의미한다. 이 예시에서는 data는 password, confirmPassword를 포함한 전체 객체이다.
- data.password === data.confirmPassword 조건은 비밀번호와 비밀번호 확인 값이 일치하는지 확인한다. 이 조건이 false이면 유효성 검사를 통과하지 못한 것으로 간주된다.
두 번째 인자 ({ path: ['confirmPassword'], message: '비밀번호가 일치하지 않습니다.' })
- 이 객체는 유효성 검사가 실패했을 때, 어떤 항목에 오류 메시지를 표시할지를 설정한다.
- path: ['confirmPassword']: 오류 메시지를 confirmPassword 필드에 연결한다. 즉, 비밀번호 확인 필드에서 오류가 발생할 때 이 메시지가 나타난다.
- message: 조건이 실패할 때 표시될 오류 메시지를 정의한다.
2. 폼 만들기
/* eslint-disable @typescript-eslint/no-unused-vars */
'use client'
import { signupSchema } from '@/app/_utils/signupSchema'
import { zodResolver } from '@hookform/resolvers/zod'
import Image from 'next/image'
import Link from 'next/link'
import { useForm } from 'react-hook-form'
type FormData = {
id: string
nickname: string
password: string
confirmPassword: string
}
export default function Signup() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(signupSchema),
mode: 'all',
})
const onSubmit = (data: FormData) => {
const { confirmPassword, ...submitData } = data
console.log('회원가입 data:', submitData)
}
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b px-4 text-center">
<Link href={'/'}>
<Image
src="/imgs/logo.png"
alt="Habit Tracker Illustration"
width={400}
height={300}
priority
/>
</Link>
<div className="w-full max-w-[400px]">
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-2">
<input
type="text"
className="rounded-full border-2 border-green-30 bg-white px-4 py-2 outline-none"
placeholder="아이디"
{...register('id')}
/>
{errors.id && (
<p className="pl-4 text-left text-sm text-red-500">
{errors.id.message}
</p>
)}
<input
type="text"
className="rounded-full border-2 border-green-30 bg-white px-4 py-2 outline-none"
placeholder="닉네임"
{...register('nickname')}
/>
{errors.nickname && (
<p className="pl-4 text-left text-sm text-red-500">
{errors.nickname.message}
</p>
)}
<input
type="password"
className="rounded-full border-2 border-green-30 bg-white px-4 py-2 outline-none"
placeholder="비밀번호"
{...register('password')}
/>
{errors.password && (
<p className="pl-4 text-left text-sm text-red-500">
{errors.password.message}
</p>
)}
<input
type="password"
className="rounded-full border-2 border-green-30 bg-white px-4 py-2 outline-none"
placeholder="비밀번호 확인"
{...register('confirmPassword')}
/>
{errors.confirmPassword && (
<p className="pl-4 text-left text-sm text-red-500">
{errors.confirmPassword.message}
</p>
)}
<button
type="submit"
className="mt-4 rounded-full bg-green-30 px-6 py-2 text-white transition hover:bg-green-40"
>
회원가입하기
</button>
</form>
<div className="text-md mt-20 flex items-center justify-center gap-4">
<p>이미 회원이신가요?</p>
<Link
href="/login"
className="text-green-40 underline decoration-green-40 underline-offset-2"
>
로그인하기
</Link>
</div>
</div>
</div>
)
}
⚠️"use client" 쓰기
Next.js에서는 기본적으로 컴포넌트가 서버 컴포넌트로 처리되는데, 서버 컴포넌트는 클라이언트 상태를 관리하거나 이벤트를 처리할 수 없다. 그래서 React Hook Form과 같이 클라이언트 측에서 상태 관리가 필요한 라이브러리를 사용할 때는 'use client'를 명시적으로 지정해 해당 컴포넌트가 클라이언트에서 실행되도록 해야 한다.
구현 결과
비밀번호 확인 부분만 빼고 잘 출력되는 것을 볼 수 있돠. 🙂
'🔥Next 뽀개기' 카테고리의 다른 글
Next.js Layout과 Firebase로 AuthGuard 구현하기(feat. cookie) (0) | 2024.12.01 |
---|---|
Next.js에 firebase 연동해서 회원가입 구현하기 (0) | 2024.11.29 |
구글 간편 로그인 구현하기 (0) | 2024.09.07 |
카카오 간편 로그인 구현하기 (4) | 2024.08.24 |
axios interceptors 설정하기(feat.cookie) (0) | 2024.08.06 |