빌드업 - 클럽 생성 (react-hook-form, useState)

박상하·2024년 11월 25일

BuildUp

목록 보기
2/4

11월 초 팀 프로젝트의 첫 번째 스프린트가 시작되었다.

첫 스프린트에서 맡은 부분은

클럽 생성
클럽 조회 (필터링, 무한스크롤)
클럽 검색

이 중 이번 포스팅은 클럽 생성에 대한 글을 작성해 보겠다.

클럽 생성 input 11개

클럽을 생성하는데 필요한 input value는 11개이다.

  • 클럽종목
  • 클럽사진
  • 클럽이름
  • 클럽연고지
  • 클럽연령대
  • 클럽성별
  • 클럽시간대
  • 클럽회비
  • 클럽전술(풋살)
  • 클럽전술(축구)
  • 영입희망포지션

각각의 value를 어떻게 받아올지 고민했다.

state로 관리를할지 react-hook-form을 사용할지

제어 컴포넌트 vs 비제어 컴포넌트

즉, React가 해당 컴포넌트의 input을 관여할 수 있게 할 것인지
말것인지 에 대한 차이다.

제어 컴포넌트는 값(state)로 input값을 추적하여 실시간으로 값을 업데이트 할 수 있고

비제어 컴포넌트는(ref)방식으로 DOM의 값을 참조할 수 있다.(실시간x)
대신, react-hook-form은 watch,error 등의 함수를 사용하여 실시간으로 값을 추적할 수 도 있다.

요구 사항 중 validation 과정에서 실시간으로 사용자의 입력값을 추적하여 경고 메세지를 알려주는 요구사항이 있었다.

그래서 완전히 react-hook-form만을 사용하기엔 무리가 있겠다 싶었다.

state로만 관리를 할까 했는데 state 값이 11개가 되면 관리가 어려울 것이라 생각했다.

react-hook-form과 state의 혼용

필자가 생각한 방법으로는 실시간으로 validaition 필요하지 않은 input이라면 react-hook-form으로 관리하고 실시간 값 추적이 필요한 input값은 state로 관리하고자 하였다.

react-hook-form
클럽종목
클럽사진
클럽연령대
클럽성별
클럽시간대
클럽전술(풋살)
클럽전술(축구)

state
클럽이미지프리뷰
클럽이름
클럽회비
클럽연고지
클럽영입포지션

코드

export default function ClubCreation() {
  const [clubCreationInputs, setClubCreationInputs] =
    useState<ClubCreationInputStateI>({
      imgPreview: "",
      clubName: "",
      clubNameError: "",
      clubFee: "0",
      clubFeeError: "",
      clubLocation: "",
      clubRecruitment: {
        futsal: [],
        soccer: [],
      },
    });

  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm<ClubCreationInputHookI>();
  const onSubmit: SubmitHandler<ClubCreationInputHookI> = (data) => {
    console.log(data);
    console.log("제출됨");
  };

  const imgFiles = watch("clubImg");

  useEffect(() => {
    if (imgFiles && imgFiles.length > 0) {
      const file = imgFiles[0];
      setClubCreationInputs((prev) => ({
        ...prev,
        imgPreview: URL.createObjectURL(file),
      }));
    }
  }, [imgFiles]);


  return (
    <div>
      <form className="flex flex-col" onSubmit={handleSubmit(onSubmit)}>
        <FusalOrSoccer register={register} />
        <ImgInput
          register={register}
          imgPreview={clubCreationInputs.imgPreview}
        />

        <NameInput
          clubCreationInputs={clubCreationInputs}
          setClubCreationInputs={setClubCreationInputs}
        />
        <LocationInput
          clubCreationInputs={clubCreationInputs}
          setClubCreationInputs={setClubCreationInputs}
        />
        <AgeInput register={register} />
        <GenderInput register={register} />
        <FrequentMatchTimeInput register={register} />
        <PriceInput
          clubCreationInputs={clubCreationInputs}
          setClubCreationInputs={setClubCreationInputs}
        />
        <TeamTaticsInput register={register} />
        <PreferredPosition
          clubCreationInputs={clubCreationInputs}
          setClubCreationInputs={setClubCreationInputs}
        />
        <button type="submit">생성 완료</button>
      </form>
    </div>
  );
}

위 코드처럼 각 input 컴포넌트를 모듈화하여 각 모듈이
react-hook-form으로 관리되면 register 객체를 넘겨주고
state로 관리되면 state,setState값을 넘겨주었다.

이렇게 했을 때 장/단점

장점
state로 관리하지 않아도 되는 함수를 분리하여 리렌더링을 최소화할 수 있다.

단점
오히려 관리에 불편함이 느껴진다. + type관리에 있어서도 통일성이 없어 두 개의 인터페이스로 분리되는 불편함.

export type ClubCreationInputHookI = {
  clubFutsalOrSoccer: "futsal" | "soccer";
  clubImg: File[];
  clubAge: string;
  clubGender: "남" | "여";
  clubFrequentMatchTime: string;
  clubPreferredPosition: string;
  clubTeamTactics: string;
};

export interface ClubCreationInputStateI {
  imgPreview: string;
  clubName: string;
  clubNameError: string;
  clubFee: string;
  clubFeeError: string;
  clubLocation: string;
  clubRecruitment: {
    futsal: string[];
    soccer: string[];
  };
}

이렇게 둘로 나누어 관리를 진행하였는데 개인적으로 좀 더 복잡한 느낌이 들었다. 또한 아직 구현은 하지 않았지만 submit 함수에 있어서 타당성 검사를 마쳤는지에 대한 검증도 두 로직을 나뉘어 AND연산을 사용하여한다는 단점도 존재한다.

해당 input이 나의 입장에서 많아 보이는거지 사실 리렌더링의 이득보다 코드 통일성의 이득이 더 크다면 State만을 사용하는게 더 좋을 수 있겠다 생각이 들었다.

팀원에게 배운점 🚣 (Object형태의 State)

좋은 팀을 만나 FE 개발 중 궁금한 점을 바로바로 여쭤볼 수 있고 정말 친절하게 답장을 해주신다. 그리고 무엇보다 실력이 좋으셔서 배울점이 많아 너무 감사하다.. 필자도 도움이 되어야할텐데 ..😂

그 중 이 과정에서 배운 점은 State의 데이터 타입을 string이나 number로만 사용하지 않고 Object로 사용했을 때의 이득이다.

이렇게 사용하면 useState를 1번만 선언하면 객체를 사용하여 다양한 key 값에 접근이 가능하다. 이 코드를 보고 많이 배웠다.

    useState<ClubCreationInputStateI>({
      imgPreview: "",
      clubName: "",
      clubNameError: "",
      clubFee: "0",
      clubFeeError: "",
      clubLocation: "",
      clubRecruitment: {
        futsal: [],
        soccer: [],
      },
    });

이런식으로 사용하면 타입에 대한 선언 역시 간편하다. 하나의 타입에서 state를 관리하기에 타입에 대해서도 한눈에 알아볼 수 있어 좋다.

setState((current)=>({...current,clubName:"새로운이름"}))

이런식으로 setter를 진행하면 된다.

get역시 state.clubName으로 접근하면된다.

단점이 있다면?

단점이 있다면 컴포넌트를 분리했더라도 state가 객체로 이루어져 있기 때문에 관여하지 않는 key가 존재하는 컴포넌트도 rerendering 된다는 점이다. 그런데 필자가 생각하기에 rerendering에 대한 고민은 이것보다 더 많~~은 state가 관여될 때 의미가 있는 거 같다. 물론 확장성을 고려해야 하지만 그렇기 때문에 개발 편의성 vs 서비스 성능의 이득을 잘 고려 해야한다는 점을 배울 수 있었다.

0개의 댓글