24. 08. 13 TIL

후... 버튼이나 에러 있으면 보여줘야 하는 것들이 있어 원래 썼던 리액트 훅 폼을 수정했다.

 

기존 코드는

'use client';
import { useForm, useWatch, FieldErrors } from 'react-hook-form';
import { formType } from '@/types/authFormType';
import { signUp } from '@/services/auth';
import {
  emailValidate,
  passwordConfirmValidate,
  passwordValidate,
} from '@/components/auth/AuthValidate';
import toast from 'react-hot-toast';
import { useRouter } from 'next/navigation';
import SignupCheckbox from './SignupCheckbox';
import { orbitron } from '../../../../public/fonts/orbitron';

function SignupForm() {
  const router = useRouter();
  const {
    register,
    handleSubmit,
    control,
    setValue,
    formState: { errors },
  } = useForm<formType>();

  const password = useWatch({
    control,
    name: 'password',
  });

  const joinForm = async (data: formType) => {
    const response = await signUp(data);
    console.log('Response from signUp:', response);
    if (response.message) {
      toast.success(response.message);
      router.replace('/login');
    }
  };

  const handleError = (errors: FieldErrors<formType>) => {
    if (errors.email?.message) {
      toast.error(errors.email.message);
    }

    if (errors.password?.message) {
      toast.error(errors.password.message);
    }

    if (errors.passwordConfirm?.message) {
      toast.error(errors.passwordConfirm.message);
    }

    if (
      errors.age14?.message ||
      errors.terms?.message ||
      errors.privacy?.message
    ) {
      toast.error('모든 체크박스를 선택해주세요');
    }
  };

  return (
    <>
      <div className='flex flex-col items-end justify-center h-screen'>
        <form onSubmit={handleSubmit(joinForm, handleError)}>
          <div className={`text-center text-2xl my-4 ${orbitron.className}`}>
            sign up
          </div>
          <div className='flex flex-col'>
            <label htmlFor='email' className='mb-1 text-black-200'>
              이메일 *
            </label>
            <input
              id='email'
              type='email'
              placeholder='예) voyageX@gmail.com'
              {...register('email', emailValidate())}
              className='text-black-900 w-[473px] h-[58px]  sm:w-[335px] rounded-lg p-2 '
              autoFocus
            />
          </div>
          <div className='flex flex-col mt-4'>
            <label htmlFor='password' className='mb-1 text-black-200'>
              비밀번호 *
            </label>
            <input
              id='password'
              type='password'
              placeholder='영문, 숫자, 특수문자 조합 8-16자'
              {...register('password', passwordValidate())}
              className='text-black-900 w-[473px] h-[58px]  sm:w-[335px]  rounded-lg p-2'
            />
          </div>
          <div className='flex flex-col my-3'>
            <label htmlFor='passwordConfirm'></label>
            <input
              id='passwordConfirm'
              type='password'
              placeholder='비밀번호를 다시 한번 입력해주세요'
              {...register(
                'passwordConfirm',
                passwordConfirmValidate(password),
              )}
              className='text-black-900 w-[473px] h-[58px]  sm:w-[335px]  rounded-lg p-2'
            />
          </div>
          <div className='flex flex-col'>
            <SignupCheckbox
              control={control}
              setValue={setValue}
              register={register}
            />
          </div>
          <button
            type='submit'
            className='bg-primary-600 w-[473px] h-[58px]  sm:w-[335px] rounded-lg p-2 mt-5'
          >
            가입하기
          </button>
        </form>
      </div>
    </>
  );
}

export default SignupForm;

이러했는데

 

쉽게 설명해보자면

import { useForm, useWatch, FieldErrors } from 'react-hook-form'; // 폼 관련 훅과 타입을 가져옴
import { formType } from '@/types/authFormType'; // 폼 데이터의 타입을 가져옴
import { signUp } from '@/services/auth'; // 회원가입 요청을 처리하는 함수
import {
  emailValidate,
  passwordConfirmValidate,
  passwordValidate,
} from '@/components/auth/AuthValidate'; // 이메일, 비밀번호 유효성 검사 함수들
import toast from 'react-hot-toast'; // 알림을 띄우기 위한 라이브러리
import { useRouter } from 'next/navigation'; // 페이지 이동을 위한 훅
import SignupCheckbox from './SignupCheckbox'; // 커스텀 체크박스 컴포넌트
import { orbitron } from '../../../../public/fonts/orbitron'; // Orbitron 폰트 가져오기

일단 임포트는 이렇게 해준다.

 

그리고 컴포넌트 내부에

const {
    register, // 폼 필드를 등록하는 함수
    handleSubmit, // 폼 제출 시 호출되는 함수
    control, // 폼의 상태를 관리하는 객체
    setValue, // 폼 필드 값을 설정하는 함수
    formState: { errors }, // 폼 필드의 에러 정보를 담고 있는 객체
  } = useForm<formType>(); // useForm 훅을 사용해 폼 관련 기능을 가져옴

useForm에서 사용할 것들을 가져와준다.

 

회원가입 컴포넌트라 비밀번호 확인하는 인풋값이 비밀번호 인풋값이랑 같은지 비교를 해야한다.

const password = useWatch({ // 특정 필드의 값을 실시간으로 관찰
    control, // control 객체를 사용
    name: 'password', // 관찰할 필드의 이름
  });

 

이제 폼 제출시 호출되는 함수는

 const joinForm = async (data: formType) => { // 폼 제출 시 호출되는 비동기 함수
    const response = await signUp(data); // 회원가입 요청을 서버에 보냄
    console.log('Response from signUp:', response); // 서버 응답을 콘솔에 출력

    if (response.message) { // 응답에 메시지가 있으면
      toast.success(response.message); // 성공 알림을 띄우고
      router.replace('/login'); // 로그인 페이지로 이동
    }
  };

이렇게 생겼다

 

에러처리는

 const handleError = (errors: FieldErrors<formType>) => { // 폼 에러를 처리하는 함수
    if (errors.email?.message) { // 이메일 필드에 에러가 있으면
      toast.error(errors.email.message); // 에러 메시지를 사용자에게 보여줌
    }

    if (errors.password?.message) { // 비밀번호 필드에 에러가 있으면
      toast.error(errors.password.message); // 에러 메시지를 사용자에게 보여줌
    }

    if (errors.passwordConfirm?.message) { // 비밀번호 확인 필드에 에러가 있으면
      toast.error(errors.passwordConfirm.message); // 에러 메시지를 사용자에게 보여줌
    }

    if (
      errors.age14?.message || // 필수 체크박스 필드에 에러가 있으면
      errors.terms?.message ||
      errors.privacy?.message
    ) {
      toast.error('모든 체크박스를 선택해주세요'); // 에러 메시지를 사용자에게 보여줌
    }
  };

이렇게 했다.

 

이제 폼 태그에는

 <form onSubmit={handleSubmit(joinForm, handleError)}> // 폼 요소 생성, 제출 시 joinForm과 handleError 호출

이렇게 사용해서 호출하고,

 

각각 이메일, 비밀번호 인풋 태그에는 

 {...register('email', emailValidate())} // 이메일 필드 등록 및 유효성 검사 적용

이렇게 달아주면 된다

 

체크박스는

 <SignupCheckbox
            control={control} // 체크박스 상태를 관리하는 control 객체 전달
            setValue={setValue} // 체크박스 값을 설정하는 setValue 함수 전달
            register={register} // 체크박스를 등록하는 register 함수 전달
          />

이렇게 있었다

더보기
더보기

setValue => 폼의 특정 필드 값을 수동으로 설정할 때 사용

어떤 체크박스를 클릭했을 때 다른 체크박스도 함께 체크되도록 하기 위함

 

control => 폼의 상태를 관리하는데 사용함

특정 필드의 값이 변경될 때마다 그 변화를 감지하거나, 다른 컴포넌트에서 폼의 상태를 공유하고 싶을 때

폼의 값이나 상태를 실ㅈ시간으로 감시하고 필요할 때마다 다른 컴포넌트와 공유하거나 업데이트 해야하는 경우

 

register => 폼 필드를 리액트 훅 폼에 등록하고 유효성 검사를 설정함

이걸 통해 폼의 각 입력 요소가 폼의 상태와 연결되며 값이 변경될 때 자동으로 폼 상태가 업데이트 됨

이메일, 비밀번호 입력 필드 등을 만들 때 사용하면 그 필드에 등록되고 입력된 값을 관리할 수 있게 됨

 

이제 그럼 체크박스 컴포넌트를 다시 보자

import { formType } from '@/types/authFormType';
import {
  Control,
  UseFormRegister,
  UseFormSetValue,
  useWatch,
} from 'react-hook-form';

import TermsOfServiceModal from './modal/TermsOfServiceModal';
import privacyPolicyAndConsentModal from './modal/privacyPolicyAndConsentModal';
import { checkboxValidate } from '../AuthValidate';

interface formProps {
  control: Control<formType>;
  setValue: UseFormSetValue<formType>;
  register: UseFormRegister<formType>;
}

function SignupCheckbox({ control, setValue, register }: formProps) {
  const age14 = useWatch({
    control,
    name: 'age14',
    defaultValue: false,
  });

  const terms = useWatch({
    control,
    name: 'terms',
    defaultValue: false,
  });

  const privacy = useWatch({
    control,
    name: 'privacy',
    defaultValue: false,
  });

  const allSelected = age14 && terms && privacy;

  const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
    const isChecked = e.target.checked;
    setValue('age14', isChecked);
    setValue('terms', isChecked);
    setValue('privacy', isChecked);
  };
  return (
    <>
      <div className='border-b my-2 pb-2'>
        <label className='font-bold'>
          <input
            type='checkbox'
            {...register('selectAll')}
            checked={allSelected}
            onChange={handleSelectAll}
            className='mr-2 '
          />
          전체동의
        </label>
      </div>
      <div className='my-2 '>
        <label>
          <input
            type='checkbox'
            {...register('age14', checkboxValidate())}
            className='mr-2 '
          />
          [필수] 만 14세 이상입니다.
        </label>
      </div>
      <div>
        <label>
          <input
            type='checkbox'
            {...register('terms', checkboxValidate())}
            className='mr-2 '
          />
          [필수] Voyage X 이용약관 동의
          <button
            type='button'
            onClick={() => TermsOfServiceModal()}
            className='ml-2 text-xs items-center '
          >
            [상세]
          </button>
        </label>
      </div>

      <div className='my-2 '>
        <label>
          <input
            type='checkbox'
            {...register('privacy', checkboxValidate())}
            className='mr-2 '
          />
          [필수] 개인정보 수집 및 이용동의
          <button
            type='button'
            onClick={() => privacyPolicyAndConsentModal()}
            className='ml-2 text-xs'
          >
            [상세]
          </button>
        </label>
      </div>
    </>
  );
}

export default SignupCheckbox;

원ㄹ ㅐ코드는 이거고

(더 줄일 수 있지만 이해가 안 가는 코드를 쓸 수는 없어 그냥 저렇게 씀)

 

import { formType } from '@/types/authFormType'; 
// formType이라는 타입을 가져옵니다. 이 타입은 폼 데이터의 구조를 정의합니다.

import {
  Control,
  UseFormRegister,
  UseFormSetValue,
  useWatch,
} from 'react-hook-form';
// react-hook-form에서 폼과 관련된 여러 기능을 가져옵니다.
// Control: 폼 상태를 관리하는데 사용
// UseFormRegister: 폼 필드를 등록하는데 사용
// UseFormSetValue: 폼 필드의 값을 설정하는데 사용
// useWatch: 특정 필드의 값을 실시간으로 관찰하는데 사용

import TermsOfServiceModal from './modal/TermsOfServiceModal';
// 이용 약관 모달 창을 불러오는 함수

import privacyPolicyAndConsentModal from './modal/privacyPolicyAndConsentModal';
// 개인정보 보호 정책 모달 창을 불러오는 함수

import { checkboxValidate } from '../AuthValidate';
// 체크박스의 유효성을 검사하는 함수

interface formProps {
  control: Control<formType>;
  setValue: UseFormSetValue<formType>;
  register: UseFormRegister<formType>;
}
// formProps라는 인터페이스를 정의합니다. 이 인터페이스는 이 컴포넌트가 받아야 할 props의 타입을 명시합니다.

임포트는 이거고

 

체크박스는 3개니까

function SignupCheckbox({ control, setValue, register }: formProps) {
// SignupCheckbox라는 함수형 컴포넌트를 정의합니다. 이 컴포넌트는 회원가입 시 체크박스를 렌더링합니다.

  const age14 = useWatch({
    control,
    name: 'age14',
    defaultValue: false,
  });
// useWatch를 사용하여 'age14'라는 필드의 값을 실시간으로 관찰합니다. 기본값은 false입니다.

  const terms = useWatch({
    control,
    name: 'terms',
    defaultValue: false,
  });
// useWatch를 사용하여 'terms'라는 필드의 값을 실시간으로 관찰합니다. 기본값은 false입니다.

  const privacy = useWatch({
    control,
    name: 'privacy',
    defaultValue: false,
  });
// useWatch를 사용하여 'privacy'라는 필드의 값을 실시간으로 관찰합니다. 기본값은 false입니다.

이렇게 만들어줌

 

그리고 셋다 참이면

  const allSelected = age14 && terms && privacy;

이거고

 

const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => { // 체크박스 상태가 변경되었을 때 호출되는 함수
  const isChecked = e.target.checked; // e.target.checked는 체크박스가 체크된 상태인지 아닌지를 나타냄. true이면 체크됨, false이면 체크 해제됨
  setValue('age14', isChecked); // 'age14'라는 폼 필드의 값을 isChecked 값으로 설정. 즉, 전체 선택이 되면 이 필드도 체크되도록 함
  setValue('terms', isChecked); // 'terms'라는 폼 필드의 값을 isChecked 값으로 설정. 전체 선택이 되면 이 필드도 체크됨
  setValue('privacy', isChecked); // 'privacy'라는 폼 필드의 값을 isChecked 값으로 설정. 전체 선택이 되면 이 필드도 체크됨
};

이건 전체 동의 체크박스를 클릭할 때 호출되는 함수로

각 개별 체크박스의 값을 전체 동의 체크박스의 상태에 따라 설정한다

 

그리고 이제 인풋인데

<input
  type='checkbox' // 체크박스 형태의 입력 필드
  {...register('selectAll')} // 이 체크박스를 폼에 'selectAll'이라는 이름으로 등록
  checked={allSelected} // 체크박스가 체크된 상태인지 여부를 제어하는 속성. 'allSelected' 변수가 true면 체크, false면 체크 해제
  onChange={handleSelectAll} // 체크박스의 상태가 변경될 때 실행되는 함수. 즉, 사용자가 체크박스를 클릭하면 'handleSelectAll' 함수가 실행됨
/>

전체는 이렇게 해주고

 

다른 체크박스는

<input
  type='checkbox' // 체크박스 형태의 입력 필드
  {...register('terms', checkboxValidate())} // 이 체크박스를 'terms'라는 이름으로 폼에 등록하고, 유효성 검사 함수도 함께 적용
/>

이렇게만 해주면 된다

 

여기는 register 함수가 이 체크박스의 상태를 자동으로 관리하고

체크박스를 프로그램적으로 제어할 필요가 없으며

사용자가 직접 조작하기 때문에 checked 속성이 필요 없다

즉, checked 속성은 체크박스를 상태에 따라 강제로 체크하거나, 해제하고 싶을 때 사용한다

 

'TIL' 카테고리의 다른 글

24. 08. 16 TIL  (0) 2024.08.16
24. 08. 14 TIL  (0) 2024.08.14
24. 08. 12 TIL  (0) 2024.08.12
24. 08. 09 TIL  (0) 2024.08.09
24. 08. 08 TIL  (0) 2024.08.08