できない.dev

React useRef の current が null で参照できない

useRef(null) の current は最初のレンダリング時には null。
DOM 参照は副作用 (useEffect) 内で読む、forwardRef 対象でないコンポーネントには ref が渡らない点をふまえて使い分ける。

#useRef#ref#null#forwardRef#useEffect

公開:

要約

useRef(null).current はマウント前は null
レンダリング関数の本体で読むと当然 null。
DOM に触るのは useEffect 以降、子コンポーネントに ref を通すなら forwardRef でラップ、の 2 点を守れば大半は解消する。

よくある原因

  1. レンダリング中に参照している: <div ref={ref}> を返す関数の中で ref.current.focus() を呼ぶと、まだ DOM が無い段階で実行されてしまう
  2. forwardRef なしの子コンポーネント: 関数コンポーネントは既定で ref props を受け取らない。<Child ref={ref} />ref は子に届かない
  3. TypeScript で型を緩めている: useRef<HTMLInputElement>(null).currentHTMLInputElement | null 型。
    null チェック漏れで実行時エラー
  4. 条件付きでマウントされる要素: {open && <Modal ref={ref} />} のようにアンマウントされる構成では ref.current がタイミングで null に戻る

解決策

1. DOM アクセスは useEffect の中で

import { useEffect, useRef } from "react";
 
export function Autofocus() {
  const inputRef = useRef<HTMLInputElement | null>(null);
 
  useEffect(() => {
    inputRef.current?.focus();   // マウント後に呼ばれる
  }, []);
 
  return <input ref={inputRef} />;
}

useRef 公式リファレンス のとおり、.current の更新は再レンダリングを引き起こさない。
レイアウト計算が要るなら useLayoutEffect を使う。

2. 子コンポーネントへの ref は forwardRef

import { forwardRef } from "react";
 
export const Field = forwardRef<HTMLInputElement, { label: string }>(
  function Field({ label }, ref) {
    return (
      <label>
        {label}
        <input ref={ref} />
      </label>
    );
  }
);

forwardRef でラップしない関数コンポーネントに ref を渡しても null のまま。
詳細は forwardRef のドキュメント を参照。

3. 条件マウントには callback ref

const handleRef = (node: HTMLDivElement | null) => {
  if (node) {
    observer.observe(node);   // マウント時
  } else {
    observer.disconnect();    // アンマウント時
  }
};
 
return open ? <div ref={handleRef}>...</div> : null;

callback ref は null での呼び出し(アンマウント)も取れるため、IntersectionObserver / ResizeObserver の登録解除に向く。
使い分けは Manipulating the DOM with Refs の章を読むとよい。

4. null 安全に書く

inputRef.current?.focus();
const width = boxRef.current?.getBoundingClientRect().width ?? 0;

TypeScript の strictNullChecks 下では ?. で安全に書くのが基本。!(非 null アサーション)は必要最小限に留める。

この記事は役立ちましたか?