Coworkers - 공통 컴포넌트 Input Form제작

오병훈·2025년 3월 3일
0
post-thumbnail

📌 공통 컴포넌트 Input Form 제작 (react-hook-form 사용)

Coworkers 프로젝트에서 공통적으로 사용되는 Input Form을 제작할 때,
폼 관리 라이브러리 중 react-hook-form을 선택하여 적용하였습니다.


✅ 1. 왜 react-hook-form을 선택했는가? (비교 분석)

공통적으로 사용될 Input Form을 제작할 때, 폼 상태 관리 방법을 고민하며 다음과 같은 대안과 react-hook-form의 장점을 비교하였습니다.

1️⃣ 기존 useState 기반 폼 관리 (비효율적)

장점

✅ 사용법이 직관적이고 간단함

단점

❌ 폼 필드가 많아질수록 불필요한 렌더링 증가
❌ 모든 입력값을 개별 useState로 관리해야 하므로 코드가 길어지고 복잡해짐


2️⃣ react-hook-form (최종 선택)

장점

불필요한 리렌더링 없이 성능 최적화 가능
기본적으로 useState를 사용하지 않음 → 폼 상태가 DOM을 통해 관리됨
register 함수로 손쉽게 폼 요소 연결 가능
errors 객체를 통해 유효성 검증 에러를 직관적으로 확인 가능

단점

❌ 초기 학습이 필요할 수 있음

🔹 결론

→ react-hook-form은 가벼우면서도 강력한 성능 최적화가 가능하므로
→ Coworkers 프로젝트의 Input Form에 적합한 선택지라고 판단하여 적용하였습니다.


🛠 2. 공통 컴포넌트 Input Form 주요 기능 정리

제작한 Input Form의 핵심 기능과 코드에서 주목할 부분을 정리하였습니다.

1️⃣ 폼 필드 등록 (register)

register를 통해 입력값을 react-hook-form에 등록

const { register } = useFormContext();

<input
  {...register(name, validationRules)} // 입력 필드 등록
  type={inputType}
  placeholder={placeholder}
/>

✅ 별도의 useState 없이도 상태가 자동으로 관리됨
✅ 유효성 검증(validationRules)을 적용할 수 있음


2️⃣ 유효성 검증 (Validation Rules 적용)

validationRules?: RegisterOptions;
<input
  {...register(name, {
    required: '이 필드는 필수 입력 항목입니다.',
    maxLength: { value: 30, message: '최대 30자까지 입력 가능합니다.' }
  })}
/>

required, maxLength 등 다양한 검증 규칙 적용 가능
errors[name]?.message를 통해 에러 메시지를 쉽게 표시


3️⃣ 비밀번호 토글 기능 (Visibility Toggle)

const [isVisibleToggle, setIsVisibleToggle] = useState(false);

<button type="button" onClick={handleToggleClick}>
  {isVisibleToggle ? <IconVisibility /> : <IconInVisibility />}
</button>

useState를 활용하여 비밀번호 보이기/숨기기 기능 제공


4️⃣ 에러 메시지 표시 (유효성 검증 실패 시)

{errors[name] && (
  <span className="text-sm text-status-danger">
    {errors[name]?.message as string}
  </span>
)}

✅ 입력값 검증 실패 시 즉시 사용자에게 피드백 제공


import { MouseEvent, MouseEventHandler, ReactNode, useState } from 'react';
import { useFormContext, RegisterOptions } from 'react-hook-form';
import IconVisibility from '@/app/components/icons/IconVisibility';
import IconInVisibility from '@/app/components/icons/IconInVisibility';

type InputProps = {
  name: string; // 필드 이름 (폼 데이터의 키)
  title?: string; // 라벨 제목
  type: string; // input 타입 (예: text, password 등)
  placeholder: string; // 플레이스홀더
  autoComplete: string; // 자동 완성 옵션
  validationRules?: RegisterOptions; // react-hook-form 유효성 검증 규칙
  backgroundColor?: string; // 입력 필드 배경색
  customButton?: ReactNode; // 추가 버튼 컴포넌트
  className?: string; // 스타일 추가
  onClick?: MouseEventHandler<HTMLInputElement>; // 클릭시 이벤트
  value?: string | undefined; // input에 들어올 value 값
  defaultValue?: string;
  readOnly?: boolean; // 직접 값을 입력할 수 없도록(날짜,시간 선택시)
};

function Input({
  name,
  title,
  type = 'text',
  placeholder,
  autoComplete,
  validationRules,
  backgroundColor = 'bg-background-secondary',
  customButton,
  className,
  onClick,
  value,
  defaultValue,
  readOnly,
}: InputProps) {
  const [isVisibleToggle, setIsVisibleToggle] = useState(false);
  const {
    register,
    formState: { errors },
  } = useFormContext();

  const isPassword = type === 'password';
  const inputType = isPassword ? (isVisibleToggle ? 'text' : 'password') : type;

  const handleToggleClick = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    setIsVisibleToggle(!isVisibleToggle);
  };

  const inputBorderClass = errors[name]
    ? 'border-status-danger' // 에러시 border 색상
    : 'border-[#F8FAFC1A]'; // 기본 border 색상

  return (
    <div className="flex flex-col gap-3">
      <label className="text-base font-medium text-text-primary" htmlFor={name}>
        {title}
      </label>

      <div className="relative">
        <input
          className={`placeholder:text-text-danger h-full w-full rounded-xl border px-4 py-[0.85rem] text-text-primary placeholder:text-lg focus:border-interaction-focus focus:outline-none ${backgroundColor} ${inputBorderClass} ${className}`}
          {...register(name, validationRules)}
          type={inputType}
          id={name}
          placeholder={placeholder}
          autoComplete={autoComplete}
          onClick={onClick}
          value={value}
          defaultValue={defaultValue}
          readOnly={readOnly}
        />
        {isPassword && customButton && (
          <div className="absolute right-4 top-2 z-20">{customButton}</div>
        )}
        {isPassword && !customButton && (
          <button
            className="absolute right-4 top-3 z-10"
            type="button"
            onClick={handleToggleClick}
          >
            {isVisibleToggle ? <IconVisibility /> : <IconInVisibility />}
          </button>
        )}
      </div>

      {errors[name] && (
        <span className="text-sm text-status-danger">
          {errors[name]?.message as string}
        </span>
      )}
    </div>
  );
}

export default Input;
profile
Front-End Developer

0개의 댓글