[모투] 제네릭을 이용한 컴포넌트

Chanyoung Park·2024년 4월 11일
0

들어가며

  • react-hook-form과 커스텀 Input컴포넌트를 사용하다보니, 커스텀 컴포넌트가 특정 Props에 종속되는 상황이 발생했다.
import { TSignUpProps } from "@/app/(account)/sign-up/page";
import React from "react";
import { Path, UseFormRegister } from "react-hook-form";

type TInputProps = {
  label: Path<TSignUpProps>;                // <- 여기
  register: UseFormRegister<TSignUpProps>;  // <- 여기
} & React.InputHTMLAttributes<HTMLInputElement>;

const Input = (props: TInputProps) => {
  ...
  return (...)
};

export default Input;
  • 위 경우, Input컴포넌트는 TSignUpProps에 해당하는 props만 받게 되어 재사용이 불가능한 종속적인 컴포넌트가 되어버린다.

해결법 (제네릭)

  • Input컴포넌트의 재사용을 위해 T~~Props를 제네릭으로 받아 사용할 수 있게 바꾸어보았다.
// component/Input.tsx
import { TSignUpProps } from "@/app/(account)/sign-up/page";
import React from "react";
import {
  Path,
  UseControllerProps,
  UseFormRegister,
  useController,
  FieldValues,
} from "react-hook-form";

type TControlProps<T extends FieldValues> = UseControllerProps<T> & {}; // FieldValues를 확장하는 제네릭으로 타입 지정

const Control = <T extends FieldValues>({ ...props }: TControlProps<T>) => {
  const { field } = useController(props);
  const inputRef = React.useRef<HTMLInputElement>(null);

  const onclick = () => {
    inputRef.current?.focus();
  };

  return (
    <div
      className="flex flex-col bg-primary-100 p-3 cursor-text"
      onClick={onclick}
    >
      <label className="cursor-text" htmlFor={props.name}>
        {props.name.toUpperCase()}
      </label>
      <input
        {...field}
        ref={inputRef}
        placeholder={props.name}
        className="bg-primary-100 outline-none"
      />
    </div>
  );
};

const Input = {
  Control,
};

export default Input;
  • 위와 같이 수정된 Input컴포넌트는, 사용하는 화면에서 type을 지정하여 사용할 수가 있게 된다.
<Input.Control<TSignUpProps>
  name={}
  control={}
</Input>


// sign-up/page.tsx
"use client";
import React from "react";
import Input from "@/components/Input";
import Button from "@/components/Button";
import { Controller, SubmitHandler, useForm } from "react-hook-form";

export type TSignUpProps = {
  email: string;
  password: string;
  confirm: string;
};

const SignUpPage = () => {
  const { handleSubmit, control, reset } = useForm<TSignUpProps>({
    defaultValues: {
      email: "",
      password: "",
      confirm: "",
    },
  });

  const onSubmit: SubmitHandler<TSignUpProps> = (data) => {
    alert(JSON.stringify(data));
  };

  return (
    <div>
      <h3 className="text-center mb-5 text-primary-500">Create an Account</h3>
      <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-5">
        // 이런식으로 사용해주면 된다!
        <Input.Control<TSignUpProps>
          name="email"
          control={control}
          rules={{ required: true }}
        />
        <Input.Control<TSignUpProps>
          name="password"
          control={control}
          rules={{ required: true }}
        />
        <Input.Control<TSignUpProps>
          name="confirm"
          control={control}
          rules={{ required: true }}
        />
        // 이런식으로 사용해주면 된다!
        <Button type="submit">Submit</Button>
      </form>
    </div>
  );
};

export default SignUpPage;
  • 잘 작동된다!
profile
더 나은 개발경험을 생각하는, 프론트엔드 개발자입니다.

0개의 댓글