본문 바로가기
코딩이야기/트러블슈팅

[React] Input 컴포넌트에서 useRef 오류가 날 때

by TaeHyeon0412 2024. 9. 9.

문제 상황

input과 오류 메세지를 컴포넌트로 만들어서 사용하였고 input에 오류 메세지가 뜨면 useRef로 포커스가 되게 하려고 했으나 포커스가 적용되지 않고 오류 메세지가 나오는 현상

 

input 컴포넌트

"use client";

interface InputProps {
  label: string;
  name: string;
  kind?: "text";
  errors?: string[];
  [key: string]: any; //input으로 오는 모든 props를 받게 해놓음
}

export default function Input({
  label,
  name,
  errors = [],
  kind = "text", //kind의 기본값은 text이고 나머지값들은 객체로 받아옴
  ...rest //input으로 오는 모든 props를 ...rest로 받음
}: InputProps) {
  return (
    <div>
      <label
        className="my-3 mb-1 block text-sm font-medium text-gray-700"
        htmlFor={name}
      >
        {label}
      </label>

      {kind === "text" ? (
        <div className="rounded-md relative flex  items-center shadow-sm">
          <input
            name={name}
            id={name}
            {...rest}
            className="appearance-none w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500"
          />
        </div>
      ) : null}

      {errors?.map((error, index) => (
        <span
          key={index}
          className="flex flex-col pt-1 pl-1 text-red-500 text-xs font-semibold"
        >
          {error}
        </span>
      ))}
    </div>
  );
}

 

login_formuseRef 적용

import Button from "../common/button";
import Input from "@/app/_components/common/input";
import React, { useEffect, useRef } from "react";
import { useFormState } from "react-dom";
import { login } from "../../(auth)/enter/action";

export default function LoginForm() {
  const emailRef = useRef<HTMLInputElement>(null);
  const [state, action] = useFormState(login, null);

  useEffect(() => {
    if (state?.fieldErrors?.email) {
      emailRef.current?.focus();
    }
  }, [state?.fieldErrors?.email]);
  
  //filedErrors가 뜨면 focus를 하게 함

  return (
    <div className="mt-16 px-4">
      <div className="flex justify-center">이미지 들어갈 공간</div>

      <div className="mt-12">
        <div className="flex flex-col items-center">
          <h5 className="text-lg text-gray-500 font-semibold">입장하기</h5>
        </div>

        <form action={action} className="flex flex-col">
          <div className="mt-1">
            <Input
              ref={emailRef}
              name="email"
              label=""
              placeholder="이메일 주소"
              errors={state?.fieldErrors.email} //useFormState의 state를 받아오고 handleForm의 return값이 출력됨
            />
            <Input
              name="password"
              label=""
              type="password"
              placeholder="비밀번호"
              errors={state?.fieldErrors.password}
            />
          </div>

          <Button text={"로그인"} type="login" />
        </form>
        
      ...

 

input에 useRef를 적용했는데 왜 오류가 날까?

 

문제 원인

함수형 컴포넌트에 ref 를 직접 전달하려고 했기 때문에 오류가 생겼고
React에서는 일반 함수형 컴포넌트에 ref를 전달할 수 없고 ref가 DOM 요소 또는 클래스 컴포넌트에만 기본적으로 연결될 수 있기 때문이라고 합니다.

 

해결 방법

React.forwardRef를 사용하여 ref를 함수형 컴포넌트에 전달하고, 해당 컴포넌트에서 특정 DOM 요소(예: <input> 태그)에 연결함으로써 문제를 해결하였습니다.
이를 통해 부모 컴포넌트가 자식 컴포넌트의 특정 DOM 요소에 접근할 수 있게 되었습니다.

forwardRef를 호출해야 하기 때문에 즉시 export 할 수 없습니다.
export default function Input (함수 선언식) ⇒ const Input(함수 표현식) 변경
forwardRef<HTMLInputElement, InputProps>를 사용하여 코드 수정

"use client";

import { forwardRef } from "react";

interface InputProps {
  label: string;
  name: string;
  kind?: "text";
  errors?: string[];
  [key: string]: any; //input으로 오는 모든 props를 받게 해놓음
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  //kind의 기본값은 text이고 나머지값들은 객체로 받아옴
  //input으로 오는 모든 props를 ...rest로 받음
  ({ label, name, errors = [], kind = "text", ...rest }, ref) => {
    return (
      <div>
        <label
          className="my-3 mb-1 block text-sm font-medium text-gray-700"
          htmlFor={name}
        >
          {label}
        </label>

        {kind === "text" ? (
          <div className="rounded-md relative flex  items-center shadow-sm">
            <input
              ref={ref} // ref를 input 요소에 연결
              name={name}
              id={name}
              {...rest}
              className="appearance-none w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500"
            />
          </div>
        ) : null}

        {errors?.map((error, index) => (
          <span
            key={index}
            className="flex flex-col pt-1 pl-1 text-red-500 text-xs font-semibold"
          >
            {error}
          </span>
        ))}
      </div>
    );
  }
);

export default Input;

 

 

 

 

오류 수정 완료!

한줄 요약 : 함수형 컴포넌트에 ref를 직접 전달할 수 없으므로 React.forwardRef를 사용하여 전달해주자