React.forwardRefで「Function components cannot be given refs」を修正する

intermediate⚛️ React2026-05-08| React 16.3以降、OS問わず、任意のバンドラー(Webpack、Vite、CRA、Next.js)

Error Message

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

エラーの内容

カスタムコンポーネントに ref をアタッチしたところ、コンソールに以下のメッセージが表示されました:

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

コードはおそらく次のような形になっているはずです:

function TextInput({ placeholder }) {
  return <input placeholder={placeholder} />;
}

// どこか別の場所で
const inputRef = useRef(null);
<TextInput ref={inputRef} placeholder="Type here" />

ref は何も動作しません。inputRef.currentnull のままで、React はその警告をログに出力しますが、目立ったエラーは発生しません。そのため、このバグは見逃しやすいのです。

なぜこのエラーが発生するのか

ref は React における通常の prop ではありません。関数コンポーネントに ref={inputRef} と記述しても、React はコンポーネントに渡される前にそれを横取りします。つまり props には一切現れません。関数コンポーネントにはインスタンスが存在しないため、ref をアタッチする場所がなく、React は ref を完全に破棄してしまいます。

クラスコンポーネントはインスタンスを持つため、この問題を回避できます。関数コンポーネントはそうではありません。だからこそ React.forwardRef() が存在します。これは明示的なオプトインであり、「このコンポーネントは ref の扱い方を知っている」と React に伝えるものです。

修正方法:React.forwardRef でラップする

コンポーネントを forwardRef でラップします。すると React は ref を props とは別に、関数の第2引数として渡します。あとは公開したい DOM 要素にそれをアタッチするだけです。

修正前(動作しない)

function TextInput({ placeholder }) {
  return <input placeholder={placeholder} />;
}

修正後(動作する)

import { forwardRef } from 'react';

const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
  return <input ref={ref} placeholder={placeholder} />;
});

export default TextInput;

親コンポーネントから直接、内部の <input> DOM ノードにアクセスできるようになります:

import { useRef } from 'react';
import TextInput from './TextInput';

function Form() {
  const inputRef = useRef(null);

  function focusInput() {
    inputRef.current?.focus();
  }

  return (
    <>
      <TextInput ref={inputRef} placeholder="Type here" />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

アロー関数を使う場合(こちらも有効)

const TextInput = forwardRef(({ placeholder }, ref) => (
  <input ref={ref} placeholder={placeholder} />
));

TextInput.displayName = 'TextInput';

アロー関数コンポーネントには必ず displayName を設定してください。設定しないと、React DevTools でコンポーネントが ForwardRef と表示されます。1つなら問題ありませんが、同じものが5つ積み重なったコンポーネントツリーをデバッグする羽目になると困ります。

ネストされた要素への ref の転送

ref は必ずしもルート要素に設定する必要はありません。呼び出し元が実際にアクセスしたい場所に配置してください:

const Card = forwardRef(function Card({ children, className }, ref) {
  return (
    <div className="card-wrapper">
      <div ref={ref} className={`card ${className}`}>
        {children}
      </div>
    </div>
  );
});

TypeScript:forwardRef の正しい型付け

ジェネリックのシグネチャは forwardRef<RefType, PropsType> です。どちらの型パラメータも重要です:

import { forwardRef, InputHTMLAttributes } from 'react';

interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {
  label?: string;
}

const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
  function TextInput({ label, ...props }, ref) {
    return (
      <div>
        {label && <label>{label}</label>}
        <input ref={ref} {...props} />
      </div>
    );
  }
);

export default TextInput;

ジェネリクスを省略すると、TypeScript は refunknown として推論します。その結果、ref を使用する箇所で型エラーが発生します。しかもそれがまったく別のファイルで起きることも多く、根本原因を特定するのが面倒になります。

よくある間違い:ref のアタッチ忘れ

これは多くの人がはまるミスです。forwardRef でラップしてコンソールの警告が消え、一見すべて正常に見えます。しかし ref.current はまだ null のままです:

// バグ:ref 引数は受け取っているが使用していない
const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
  return <input placeholder={placeholder} />; // ref がアタッチされていない!
});

引数として ref を受け取ることは、作業の半分に過ぎません。実際に機能させるためには、DOM 要素または別の forwardRef コンポーネントにそれを渡す必要があります。

修正が正しく適用されたか確認する

  • ブラウザのコンソールから警告が消えている。
  • マウント後に inputRef.currentnull でなくなっている。簡単な useEffect ログで確認できます:
useEffect(() => {
  console.log('ref:', inputRef.current); // DOM 要素が表示されるはず(例:<input>)
}, []);
  • React DevTools でコンポーネントが名前付きで表示される(ForwardRef ではなく TextInput)。これで displayName が設定されていることが確認できます。
  • .focus().scrollIntoView() といった命令的な呼び出しがエラーなく動作する。

クイックリファレンス

  • 通常の関数コンポーネント + ref prop → 警告が出て ref は null
  • forwardRef でラップする → 第2引数が ref になるので、DOM ノードにアタッチする
  • TypeScript:forwardRef<HTMLElement, Props> ジェネリクスを使用する(どちらも重要)
  • アロー関数コンポーネントには DevTools で読みやすくなるよう displayName を設定する
  • 警告は消えたのに ref がまだ null?ラップは正しいが、内部で ref のアタッチを忘れている

Related Error Notes