react-hook-form 에러 해결 과정(feat. Ref와 비제어 컴포넌트)

Daisy🌼·2022년 11월 23일
2

🤔 프로젝트에 유효성 검사 라이브러리인 React Hook Form 을 도입했는데 ref와 관련된 에러를 찾는 데 어려움을 겪었다. 그래서 React Hook Form의 동작 방식을 간단하게 알아보고, 에러의 원인을 찾아 해결한 과정을 기록하기로 했다.

📌 어떻게 만들어졌을까

  • 우선 공식 문서에서 이 라이브러리의 탄생 배경과 동작 방식을 간단하게 알아볼 수 있었습니다.

React Hook Form relies on uncontrolled form, which is the reason why the register
function capture ref and controlled component has its re-rendering scope with Controller or useController.

This approach reduces the amount of re-rendering that occurs due to a user typing in input or other form values changing at the root of your form or applications.

React Hook Form is based on Uncontrolled Components, which gives you the ability to build an accessible custom form easily.

  • 공식 문서의 내용을 종합해보면, React Hook Form은 ref를 사용한 비제어 컴포넌트를 기반으로 작동하며, 이를 통해 props변경으로 인한 리렌더링을 줄여 성능을 개선시킨 폼 컴포넌트입니다.

📌 제어? 비제어?

  • 리액트에서 컴포넌트는 제어 컴포넌트와 비제어 컴포넌트로 나눌 수 있습니다.

✔️ 제어 컴포넌트

  • 우선 공식문서에서 설명하는 제어 컴포넌트는 다음과 같습니다.

우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있습니다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.

  • 제어 컴포넌트는 리액트에 의해 입력값이 관리되는(제어되는) 컴포넌트입니다.

  • 예를 들어 input 과 같은 입력 요소는 다양한 타입과 속성을 가질 수 있으며, typesubmit인 입력 요소와 연결되면 입력값에 접근할 수 있습니다. 즉 내부적으로 값의 상태를 저장하고 상태에 접근할 수 있습니다.

  • 하지만 리액트에서는 일반적으로 state를 사용해 입력 이벤트가 발생하면 이벤트 핸들러가 호출되면서 setState()state를 업데이트하는 과정을 거칩니다.

  • 신뢰 가능한 단일 출처 (Single Source of Truth, SSOT) 원칙에 부합하고자 이러한 양식 요소들을 내부 속성이 아닌 리액트에 의해 상태를 관리할 수 있는데, 이러한 컴포넌트를 제어 컴포넌트라고 합니다.

  • 하지만 제어 컴포넌트는 state가 업데이트될 때마다 컴포넌트가 리렌더링됩니다. 또한 리액트에서는 부모 컴포넌트의 state가 변경되면 이를 props로 전달받은 자식 컴포넌트도 같이 리렌더링되기 때문에 사용자의 입력을 받는 폼 컴포넌트는 사용자 입력이 빈번하기 일어나는 만큼 불필요한 리렌더링이 자주 일어나고, 잦은 렌더링은 성능에도 영향을 줍니다.

✔️ 비제어 컴포넌트

  • 반대로 비제어 컴포넌트는 리액트의 상태 관리 흐름에서 벗어나 DOM 자체에서 데이터를 직접 조작하고 관리하는 컴포넌트입니다.

  • 비제어 컴포넌트에서 DOM에 접근하기 위해 리액트에서는 ref라는 객체를 제공합니다. 그리고 ref 객체의 current 프로퍼티의 값을 변경하는데, 값을 업데이트해도 컴포넌트가 리렌더링되지 않기 때문에 렌더링 횟수를 줄여줍니다.

🤔 공식문서에 의하면, 클래스 컴포넌트에서는 React.createRef()를 호출해 ref를 프로퍼티로 추가해서 컴포넌트 인스턴스에서 ref에 접근할 수 있다고 한다. 하지만 함수형 컴포넌트에는 인스턴스가 없기 때문에 useRef라는 Hook을 호출해 객체를 만들어 컴포넌트에 부착한다고 한다.

  • 그리고 비제어 컴포넌트는 state를 사용하는 리액트의 데이터 관리 흐름에서 벗어나기 때문에, 부모 컴포넌트에서 조작하는 ref를 자식 컴포넌트에 전달해도 변경값이 반영되지 않습니다. 따라서 전달하려고 하면 에러가 발생합니다.

📌 Ref 관련 에러

  • React Hook Form 라이브러리를 사용하면서 다음과 같은 ref 관련 에러가 발생했습니다.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

함수형 컴포넌트는 refs를 받을 수 없습니다. 해당 ref에 접근하려고 하면 실패할 것입니다. React.forwardRef()를 사용하시겠습니까?

  • 앞에서 보았듯이 비제어 컴포넌트는 DOM 데이터를 직접 조작하기 위해 사용하는 것인데, 이를 props로 전달하려고 하니 에러가 발생하는 것입니다.

📌 forwardRef로 해결하기!

  • 마찬가지로 React Hook Form은 내부적으로 ref 객체를 사용하고 있으므로 이를 자식 컴포넌트에 전달하려고 하면 위와 같은 에러가 발생합니다.

  • 대부분의 경우 이러한 상황이 발생하지 않지만, React Hook Form과 같이 ref를 기반으로 동작하는 라이브러리를 사용한다면 고려할 수 있는 문제입니다.

  • 리액트에서는 이를 위해 Forwarding Ref라는 기법을 제공해 ref를 전달할 수 있습니다. 즉 forwardRef() 함수로 ref를 전달하는 부모 컴포넌트와 전달받는 자식 컴포넌트를 감싸주면 props를 전달하는 것처럼 전달해 사용할 수 있습니다.

// forwardRef 함수를 불러옵니다. 
import React, { FC, forwardRef } from 'react'

// Input이라는 컴포넌트 전체를 forwardRef 함수로 감싸주고, ref를 받아옵니다.
const Input: FC<Props> = forwardRef(({ inputName, ...props }, ref) => {
	// ref={ref}로 S.Input이라는 자식 컴포넌트에 전달할 수 있습니다.
  return <S.Input name={inputName} {...props} ref={ref} autoComplete="off" />
}) 

정리

  • 리액트에서 DOM을 직접 조작하는 컴포넌트를 비제어 컴포넌트라고 합니다.

  • 비제어 컴포넌트는 ref 객체를 통해 직접 관리하는데, 자식 컴포넌트로 전달할 수 없습니다.

  • 만약 ref 객체의 값을 전달해야 하는 경우가 발생하면 forwardRef()를 사용했습니다.


참고 자료

React - 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리

리액트 ref 와 forwardRef (with. react-hook-form)

[React] react-hook-form 사용시 발생하는 ref warning

profile
커피와 재즈를 좋아하는 코린이 | 좋은 글 좋은 코드를 쓰고 싶습니다

0개의 댓글