[React Hook Form] props drilling 줄이기 | useFormContext, FormProvider

boyeonJ·2023년 5월 23일
11

Tool/Library

목록 보기
7/12
post-thumbnail

useFormContext / FormProvider

useFormContext / FormProvider는 무엇일까요? 🧐

FormProvider는 React Hook Form에서 제공하는 컴포넌트로, React의 Context API를 기반으로 구현되었습니다. FormProvider는 상위 컴포넌트에서 하위 컴포넌트로 폼 데이터와 관련된 상태와 로직을 전달하기 위해 사용됩니다.

useFormContext는 React Hook Form의 컨텍스트에 접근하기 위한 커스텀 훅입니다. useFormContext를 사용하면 컨텍스트를 prop으로 전달하는 것이 불편한 깊은 중첩 구조에서도 컨텍스트에 접근할 수 있습니다. 이 훅을 사용하면 useForm에서 반환하는 모든 메서드와 속성을 가져올 수 있습니다. 즉, useForm의 반환값을 그대로 사용할 수 있습니다.

useFormContext를 사용하기 위해서는 폼을 FormProvider 컴포넌트로 감싸주어야 합니다. FormProvider 컴포넌트에 useForm에서 반환한 메서드와 속성을 전달하면 됩니다. 그런 다음 useFormContext를 호출하여 해당 메서드와 속성을 가져올 수 있습니다.


사용 방법(순서대로 따라해보세요!)

1. useForm() 훅을 사용하여 폼 메서드(methods)를 가져옵니다.

import { useForm, FormProvider } from 'react-hook-form';
...
const methods = useForm();
...

2. FormProvider 의 props로 (1)에서 가져온 폼 메서드(methods)를 넘겨줍니다.

<FormProvider {...methods}>

3. 폼 컴포넌트들을 FormProvider 내부에 작성합니다.

폼 컴포넌트를 포함한 컴포넌트를 childComponent로 사용하게 되면, 원래는 props를 넘겨야 하지만, FormProvider로 감싼다면 따로 props를 넘기지 않아도 됩니다.(아무것도 해주지 않아도 되요!)

<FormProvider {...methods}>
	//폼 컴포넌트가 들어있는 childComponent로
  	<ChildComponent />
</FormProvider>

3. childComponent 내부에서 useFormContext를 통해 useForm의 반환값을 그대로 사용할 수 있습니다.

여기서 중요한점은 ⭐⭐props drilling⭐⭐ 없이 사용 가능하다는 점입니다.

const ChildComponent = () => {
	const { register, handleSubmit } = useFormContext();
	const onSubmit = (data) => console.log(data)
    
  	return(
    	<form onSubmit={handleSubmit(onSubmit)}>
        	<input {...register("test")} />
        	<input type="submit" />
      	</form>
    )
}

전체 예시 코드

import React from 'react';
import { useForm, FormProvider, useFormContext } from 'react-hook-form';

const ParentForm = () => {
  const methods = useForm();

  return (
    <FormProvider {...methods}>
      <ChildForm />
    </FormProvider>
  );
};

const ChildForm = () => {
  const { register, handleSubmit } = useFormContext();
  const onSubmit = (data) => console.log(data)
  
  return (
    <form onSubmit={methods.handleSubmit(onSubmit)}>
      <input type="text" name="firstName" {...methods.register('firstName')} />
      <input type="text" name="lastName" {...methods.register('lastName')} />
      <button type="submit">Submit</button>
    </form>
  )
}

const App = () => {
  return (
    <div>
      <h1>My Form</h1>
      <ParentForm />
    </div>
  );
};

export default App;

성능 이슈에 대한 고민 🤔

보통 react의 context를 쓰면 불필요한 리랜더링이 발생하게 되어 성능에 이슈를 야기한다는 말이 있다. 따라서 react-hook-form의 FormProvider/useFormContext도 성능이슈를 야기할까 고민이 되었다.

만약 아래와 같은 깊은 계층 구조를 가지는 컴포넌트가 있다고 생각했을때 실제 FormProvider는 detailSheet에서 감싸주고 가장 하위 컴포넌트인 Product, CouterInput 내부의 CustomInput 컴포넌트에서 useFormContext를 사용한다고 가정했을때, CustomInput의 input을 수정하면 부모 컴포넌트들도 재랜더링이 될까? 답은 아니다!!

그 이유는 FormProvider와 useFormContext를 사용하는 핵심 개념인 로컬 폼 상태 을 활용하여 최적화 하였기 때문입니다. 로컬 폼 상태는 해당 컴포넌트가 직접적으로 의존하는 폼 필드에 대해서만 관리하고 업데이트하므로, 다른 컴포넌트의 리렌더링에 영향을 받지 않습니다. 따라서, CustomInput 컴포넌트의 성능 이슈는 해당 필드의 변경에 따라 발생하며, 다른 부모나 형제 컴포넌트들의 리렌더링과는 독립적입니다.

🚫 그러나 깊은 계층 구조를 가지는 컴포넌트에서는 여전히 주의해야 할 점이 있습니다.

만약 부모 컴포넌트가 FormProvider로 감싸져 있고, 그 하위에 여러 개의 자식 컴포넌트가 있고, 그 자식 컴포넌트들 중에서 useFormContext를 사용하는 컴포넌트가 있다면, 해당 useFormContext를 사용하는 컴포넌트의 로컬 폼 상태 변경이 발생하면 그 컴포넌트와 그의 하위 컴포넌트들이 리렌더링될 수 있습니다. 이는 React의 컴포넌트 트리에서 상위로의 리렌더링 전파로 인해 발생합니다.
읽어보기

따라서, 깊은 계층 구조를 가지는 컴포넌트에서는 useFormContext를 사용하는 컴포넌트의 성능에 영향을 주는 요소들을 최적화하는 것이 중요합니다. 필요한 경우 React.memo나 shouldComponentUpdate를 사용하여 부모 컴포넌트의 리렌더링을 최적화하고, useFormContext를 사용하는 컴포넌트 자체의 성능을 개선할 수 있습니다.

🗣 결국엔 context는 부모 컴포넌트들이 아닌 하위 컴포넌트들이 재랜더링 될 수 있으니 주의해야 한다!

0개의 댓글