LoginSignup
18
7

More than 1 year has passed since last update.

React-Hook-FormでRefを自作コンポーネントに渡したときのエラー・対処&改善

Last updated at Posted at 2022-06-04

はじめに

本業でNext.js(React),TypeScriptを使用してて、React-Hook-Formを実装する際にrefを含むregisterを自作コンポーネントを渡した時に、
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
とエラーが出てのでその対処法についてのアウトプットです。

また、子コンポーネントにinputの属性含むpropsの型定義が冗長になってたのでrefを含むComponentPropsWithoutRefを使って対処・リファクタリングも行いました。

refについての説明は省いております。

対処したコード

今回は自作したlabel付きのInputコンポーネントに対してバリデーションを実装したので、labelの型を含ませた子コンポーネントになっております。


type LabelProps = {
  label: string;
}

type ChildProps = ComponentPropsWithoutRef<'input'> & LabelProps;

const ChildInput = React.forwardRef<HTMLInputElement, ChildProps>(
({ label, ...props }, ref) => {

  return (
    <div>
      <p>{label}</p>
      <input placeholder={label} {...props} ref={ref} />
    </div>
  );
});

ChildInput.displayName = "ChildInput"

該当エラー

親コンポーネント

import React, { FC } from "react";
import {useForm, SubmitHandler} from "react-hook-from";
import ChildInput from "./ChildInut";

const Parent: FC = () => {

  const {
      handleSubmit,
      register,
      formState: { errors },
	  } = useForm<{parent: string}>({
	    defaultValues: { parent: ''},
	  });

  const onSubmit: SubmitHandler = (data) => {
      console.log(data)
   };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
  	  <ChildInput
        {...register('parent', { required: true })}
        label='子供にRefを投げる!!'
      />
	  <button>送信</button>
    </form>
  )
export default Parent

子コンポーネント


import React, { FC } from "react";

type ChildProps = {
  name: string;
  type: string;
  value: string;
  onChange: (e: ChangeEvent<HTMLInputElement>) => void;
  label: string;
}


const ChildInput: FC<ChildProps> = ({ label, name, type, value, onChange }) => {

  return (
    <div>
      <p>{label}</p>
      <input
        name={name}
        type={type}
        value={value}
        onChange={onChange}
        placeholder={label}
      />
    </div>
  );
};

export default ChildInput;

以上の子コンポーネントにrefを含むregisterをそのまま渡すと関数コンポーネントにrefを渡すことができないとエラーが表示されます。
なのでReact.fowardRefを使ってねと言っているので自作のコンポーネントに対してReact.fowardRefを使ってみましょう。

また、各HTML要素の属性とrefの型を含むComponentPropsWithoutRefを使ってスッキリさせていきましょう。

解決


- import React, { FC } from "react";
+ import React, { forwardRef, ComponentPropsWithoutRef } from "react";

- type ChildProps = {
-   name: string;
-   type: string;
-   value: string;
-   onChange: (e: ChangeEvent<HTMLInputElement>) => void;
-   label: string;
- }

+ type LabelProps = {
+   label: string;
+ }

+ type ChildProps = ComponentPropsWithoutRef<'input'> & LabelProps;

- const ChildInput: FC<ChildProps> = ({ label, ...props }) => {
+ const ChildInput = forwardRef<HTMLInputElement, ChildProps>(
+ ({ label, ...props }, ref) => {

  return (
    <div>
      <p>{label}</p>
-       <input
-         name={name}
-         type={type}
-         value={value}
-         onChange={onChange}
-         placeholder={label}
-       />
+       <input placeholder={label} {...props} ref={ref} />
    </div>
  );
- }
+ });

+ ChildInput.displayName = "ChildInput"

export default ChildInput;

詳細

・ forwardRefとは

~ Reactのドキュメントから引用 ~
コンポーネントが ref を受け取って、それをさらに下層の子に渡せる(つまり、ref を “転送” できる)ようになります。

ドキュメントにもあるように、自作したコンポーネントのDOM要素にrefを渡す時に使います。

・ ComponentPropsWithoutRefについて

ref要素を含むコンポーネントのpropsの型を作成することが出来る。
同様にComponentPropsWithRefでも使用できますが、違いとしてはComponentPropsWithoutRefと比べてクラスコンポーネントにも対応しているそうです。

以下はReactの型定義から参照したものです。

ComponentPropsWithoutRef/ComponentPropsWithRef 型の違い

 type ComponentPropsWithRef<T extends ElementType> =
        T extends ComponentClass<infer P>
            ? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
            : PropsWithRef<ComponentProps<T>>;

 type ComponentPropsWithoutRef<T extends ElementType> =
       PropsWithoutRef<ComponentProps<T>>;

・ ESLint Error: Component definition is missing display name eslint(react/display-name)

+ ChildInput.displayName = "ChildInput"

ここの箇所については、ESLintにデバック時の関数名が無いとエラーが出たの追加しました。
Component definition is missing display name eslint(react/display-name)

おそらくforwardRefで返す関数が無名関数になっている影響ですね。

まとめ

  • refをコンポーネントに渡す時は、fowardRefでコンポーネントをラップすることでコンポーネント内のDOMにrefを渡すことができる。

  • TypeScriptを使用している場合は、ComponentPropsWithRefあるいはComponentPropsWithoutRefrefと該当のHTML属性を含んだpropsを子コンポーネントが受け取ることが出来る。

ComponentPropsWithoutRefでrefと各HTML要素にも対応してたので感動しました泣
おかげでコードがスッキリになりました✨

引き続きTypeScriptとReactについて学習していきます!!

18
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
7