はじめに
Reactは通常、ステート(state)とプロップス(props)を使って画面を更新する「宣言的」なUIライブラリです。しかし開発をしていると、特定の入力フォームにフォーカスを当てたり、特定の位置までスクロールさせたりといった、ブラウザのDOM要素を直接操作したい場面に遭遇します。
この記事では、ReactにおいてDOMを操作するための標準的なフックである useRef の使い方を解説します。
なお、本記事は React 19.2 を前提として記述しています。React 19系では ref の扱いが従来よりも簡潔になっているため、その点についても触れていきます。
useRefとは何か
useRef は、再レンダリングを発生させずに情報を保持しておくためのフックです。コンポーネントが再描画されてもリセットされない「値を書き換えられる箱」のようなものだとイメージしてください。
DOM操作においては、この「箱」の中にHTML要素(DOMノード)への参照を格納するために使用します。
基本的なDOM操作の実装手順
DOMを操作するためには、以下の3つのステップを踏みます。
-
useRefフックをインポートし、初期値nullで宣言する。 - 操作したいJSXタグの
ref属性に、作成した変数を渡す。 - イベントハンドラ内などで
ref.currentを通じてDOMメソッドを呼び出す。
処理の流れ(Mermaid図解)
Reactがコンポーネントを画面に描画し、ref にDOMノードが格納されるまでの流れを整理します。
実践例:テキストボックスへのフォーカス制御
ボタンをクリックすると、入力欄(input要素)に自動的にカーソルが当たり、入力可能な状態になる機能を実装してみましょう。
import { useRef } from 'react';
export default function Form() {
// 1. refオブジェクトを作成(初期値はnull)
const inputRef = useRef(null);
function handleClick() {
// 3. ref.currentでDOMノードにアクセスし、focus()メソッドを実行
// inputRef.currentは実際の <input> DOM要素を指しています
if (inputRef.current) {
inputRef.current.focus();
}
}
return (
<>
{/* 2. 操作したい要素のref属性に渡す */}
<input ref={inputRef} type="text" placeholder="ここに入力" />
<button onClick={handleClick}>
入力欄にフォーカスする
</button>
</>
);
}
このコードでは、ブラウザが画面に <input> を表示した直後に、Reactが inputRef.current にそのDOMノードを代入します。ユーザーがボタンを押す頃には current に中身が入っているため、 focus() メソッドを呼び出すことができます。
React 19での変更点:子コンポーネントへのref渡し
これまで(React 18以前)は、自作のコンポーネントに対して ref を渡したい場合、 forwardRef という特別な関数でコンポーネントをラップする必要がありました。
React 19からはこの制限が撤廃され、 ref を通常のプロップス(props)と同じように扱うことができます。
React 19.2 での書き方
import { useRef } from 'react';
// 子コンポーネント:refをpropsとして受け取るだけ
function MyInput({ placeholder, ref }) {
return <input ref={ref} placeholder={placeholder} />;
}
// 親コンポーネント
export default function Parent() {
const inputRef = useRef(null);
return (
<>
{/* forwardRefなしで直接refを渡せる */}
<MyInput ref={inputRef} placeholder="React 19スタイル" />
<button onClick={() => inputRef.current.focus()}>
フォーカス
</button>
</>
);
}
これにより、コードがより直感的でシンプルになりました。
DOM操作時の重要な注意点
DOM操作は強力ですが、Reactの管理外で画面を書き換える行為です。以下の点に注意してください。
1. レンダリング中にrefを読み書きしない
ref.current の変更は再レンダリングをトリガーしません。そのため、コンポーネントの表示内容(JSX)を決める計算の途中で ref.current を読み書きしてはいけません。予期せぬ挙動の原因になります。
DOM操作は必ず、レンダリングが終わった後である「イベントハンドラ」や「useEffect」の中で行います。
2. nullチェックを行う
ref.current の初期値は null です。また、条件付きレンダリングなどで要素が画面に存在しない場合も null になる可能性があります。
TypeScriptを使用している場合や、要素の存在が不確定な場合は、必ず inputRef.current?.focus() のようにオプショナルチェーンを使うか、if文で存在確認を行いましょう。
まとめ
- DOM操作には
useRefを使用し、ref.currentを通じてアクセスする。 - React 19.2では
forwardRefが不要になり、コンポーネント間でのrefの受け渡しが簡単になった。 - DOM操作はあくまで「逃げ道」であり、基本はReactのステート管理で解決できないか検討する。
- レンダリング中にはアクセスせず、イベントハンドラやuseEffect内で操作する。
useRef を適切に使いこなすことで、Reactの宣言的な良さを保ちつつ、必要な場面で柔軟なUI操作を実現できます。ぜひ活用してみてください。