Page cover image

Form 컴포넌트 구현하기 (+ shadcn/ui)

react-hook-form과 shandcn/ui를 사용해 form 컴포넌트 구현하기

로그인 페이지를 구현한다고 가정해보자.

구현해야 할 기능

  • 이메일, 패스워드 입력 👉🏻 input, form 컴포넌트로 구현, 유효성 검사

  • 로그인 버튼 👉🏻 입력한 이메일과 패스워드를 서버에 전달 후 결과값 렌더링

  • 회원가입 버튼 👉🏻 회원가입 페이지로 이동


React Hook Form - shadcn/ui

Form의 기본적인 구조는 다음과 같다.

<Form>
  <FormField
    control={...}
    name="..."
    render={() => (
      <FormItem>
        <FormLabel />
        <FormControl>
          { /* Your form field */}
        </FormControl>
        <FormDescription />
        <FormMessage />
      </FormItem>
    )}
  />
</Form>
  • Form

  • FormField

  • FormItem

  • FormLabel

  • FormControl

  • FormDescription

  • FormMessage

formSchema 만들기

"use client"

import { z } from "zod"

const formSchema = z.object({
  email: z.string().email({
    message: "유효하지 않은 이메일 주소입니다.",
  }),
  password: z.string().min(6, {
    message: "비밀번호는 최소 6자 이상이어야 합니다.",
  }),
});

useform을 이용해 form 정의하기

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { signIn } from "next-auth/react";

type FormData = z.infer<typeof formSchema>;

export function Page() {
  // 1. Define your form.
  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValue: {
      email: "",
      password: "",
    },
  });

  // 2. Define a submit handler.
  const onSubmit = async (data: FormData) => {
    const { email, password } = data;

    const redirect_uri = (searchParmas.get("redirect_uri") as string) || undefined;
    const response: any = await singIn("credentials", {
      email,
      password,
      redirect: false,
      callbackUrl: redirect_uri,
    });

    if (!response.ok) {
      if (response.status === 401) {
        if (response.error === "USER_NOT_FOUND") {
          setOpenConfirmRegister(true); // AlertDialog 컴포넌트를 열고 닫기 위한 setState
          return;
        } else if (response.error === "PASSWORD_INCORRECT") {
          alert("비밀번호가 일치하지 않습니다.");
          return;
        }
      }

      alert("로그인에 실패했습니다. 다시 시도해주세요.");
      return;
    }
  };
}

form 컴포넌트 작성하기

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="email"
      render={({ field }) => (
        <FormItem>
          <FormControl>
            <Input placeholder="이메일" {...field} type="email" />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
    <Button type="submit">
      로그인
    </Button>
  </form>
</Form>;

라우터 이동 핸들러 함수

<div onClick={onRouteRegister}>또는 회원가입</div>
const onRouteRegister = async (e: React.MouseEvent) => {
  const redirect_uri = (searchParams.get("redirect_uri") as string) || undefined;
  router.push(`/register/?email=${form.getValues("email")}&redirect_uri=${redirect_uri}`);
};
  • 이메일 input에 값이 없다면 👉🏻 localhost:3000/register?email=&redirect_uri=undefined

  • 이메일 input에 값이 있다면 👉🏻 localhost:3000/register?email=bienprltd@gmail.com&redirect_uri=undefined

회원정보가 없거나, 일치하지 않는 경우

회원정보가 없는 경우

onSubmit 함수 안에서 확인할 수 있다. 회원정보가 없는 경우, 아래 조건문에 걸리게 된다.

if (!response.ok) {
     if (response.status === 401) {
        if (response.error === "USER_NOT_FOUND") {
          setOpenConfirmRegister(true);
const [openConfirmRegister, setOpenConfirmRegister] = useState(false);

shadcn/ui의 alertDialog 컴포넌트를 이용해 결과를 보여준다.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";

<AlertDialog open={openConfirmRegister} onOpenChange={setOpenConfirmRegister}>
  <AlertDialogContent>
    <AlertDialogHeader>
      <ExclamationIcon/>
      <AlertDialogTitle>가입되지 않은 이메일입니다.</AlertDialogTitle>
      <AlertDialogDescription>회원가입을 진행하시겠습니까?</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>취소</AlertDialogCancel>
      <AlertDialogAction onClick={onRouteRegister}>회원가입</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

이메일, 패스워드가 일치하지 않는 경우

else if (response.error === "PASSWORD_INCORRECT") {
  alert("비밀번호가 일치하지 않습니다.");
  return;
}

Last updated