후... 버튼이나 에러 있으면 보여줘야 하는 것들이 있어 원래 썼던 리액트 훅 폼을 수정했다.
기존 코드는
'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 |