// 画面表示時に自動フォーカス const nameRef = useRef<HTMLInputElement>(null) useEffect(() => { nameRef.current?.focus() }, []) // Enter で次フィールドへ移動 const moveFocus = (e: React.KeyboardEvent<HTMLInputElement>, next: React.RefObject<HTMLInputElement | HTMLButtonElement | null>) => { if (e.key === 'Enter') { e.preventDefault(); next.current?.focus() } } // 子コンポーネントに ref を渡す const InputField = forwardRef<HTMLInputElement, Props>(({ label, onChange }, ref) => ( <input ref={ref} onChange={onChange} /> ))
Reactでフォームを作るとき、こんな操作を実現したい場面があると思います。
- 画面表示と同時に最初の入力欄にフォーカスが当たる
- Enterキーで次の入力欄に移動できる
- 送信後にフォームがクリアされ、先頭の入力欄にフォーカスが戻る
キーボードだけで操作できるフォームはUXとして優しいですね。useRef・useEffect・forwardRef を使って実装する方法を解説します。
完成形はこちらです。

構成
2つのコンポーネントで構成します。
App.tsx:フォームのロジックとrefを管理する親コンポーネントInputField.tsx:forwardRefで ref を受け取る子コンポーネント
InputField.tsx:forwardRefで ref を受け取る
まず子コンポーネントから作ります。子コンポーネントの <input> に外から ref を渡したいときは forwardRef を使います。
import { forwardRef } from 'react' type Props = { label: string value: string placeholder?: string onChange: (e: React.ChangeEvent<HTMLInputElement>) => void onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void } const InputField = forwardRef<HTMLInputElement, Props>( ({ label, value, placeholder, onChange, onKeyDown }, ref) => ( <div className="field"> <label>{label}</label> <input ref={ref} value={value} placeholder={placeholder} onChange={onChange} onKeyDown={onKeyDown} /> </div> ) ) InputField.displayName = 'InputField' export default InputField
forwardRef<HTMLInputElement, Props> の第1型引数が ref の型、第2引数が Props の型です。displayName を設定しておくと React DevTools で名前が表示されます。
App.tsx:フォーカス制御のロジック
useRef で DOM への参照を持つ
const nameRef = useRef<HTMLInputElement>(null) const emailRef = useRef<HTMLInputElement>(null) const submitRef = useRef<HTMLButtonElement>(null)
useRef<HTMLInputElement>(null) で入力欄への参照を作ります。ref.current から DOM 要素に直接アクセスできます。
useEffect で初期フォーカス
useEffect(() => { nameRef.current?.focus() }, [])
依存配列を [] にすることで初回レンダリング時だけ実行されます。?. はオプショナルチェーニングで current が null のときは何もしません。
Enter で次フィールドへ移動
const moveFocus = ( e: React.KeyboardEvent<HTMLInputElement>, next: React.RefObject<HTMLInputElement | HTMLButtonElement | null> ) => { if (e.key === 'Enter') { e.preventDefault() next.current?.focus() } }
e.preventDefault() でブラウザのデフォルト動作(フォーム送信)を止めてから、次の要素にフォーカスを移します。移動先が input でも button でも同じ関数で動くよう、union 型で受け取っています。
送信後にフォーカスをリセット
const handleSubmit = (e: React.FormEvent) => { e.preventDefault() setName('') setEmail('') nameRef.current?.focus() }
setState でフォームをクリアした後、nameRef.current?.focus() で先頭の入力欄に戻します。
JSX で ref と onKeyDown を渡す
<InputField ref={nameRef} label="名前" value={name} placeholder="山田 太郎" onChange={(e) => setName(e.target.value)} onKeyDown={(e) => moveFocus(e, emailRef)} /> <InputField ref={emailRef} label="メールアドレス" value={email} placeholder="example@email.com" onChange={(e) => setEmail(e.target.value)} onKeyDown={(e) => moveFocus(e, submitRef)} /> <button ref={submitRef} type="submit"> 送信 </button>
onKeyDown に次の移動先 ref を渡します。名前フィールドは emailRef、メールフィールドは submitRef へ。
App.tsx 全体
import { useEffect, useRef, useState } from 'react' import InputField from './InputField' function App() { const [name, setName] = useState('') const [email, setEmail] = useState('') const nameRef = useRef<HTMLInputElement>(null) const emailRef = useRef<HTMLInputElement>(null) const submitRef = useRef<HTMLButtonElement>(null) useEffect(() => { nameRef.current?.focus() }, []) const moveFocus = ( e: React.KeyboardEvent<HTMLInputElement>, next: React.RefObject<HTMLInputElement | HTMLButtonElement | null> ) => { if (e.key === 'Enter') { e.preventDefault() next.current?.focus() } } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() setName('') setEmail('') nameRef.current?.focus() } return ( <div className="container"> <h1>お問い合わせ</h1> <form onSubmit={handleSubmit}> <InputField ref={nameRef} label="名前" value={name} placeholder="山田 太郎" onChange={(e) => setName(e.target.value)} onKeyDown={(e) => moveFocus(e, emailRef)} /> <InputField ref={emailRef} label="メールアドレス" value={email} placeholder="example@email.com" onChange={(e) => setEmail(e.target.value)} onKeyDown={(e) => moveFocus(e, submitRef)} /> <button ref={submitRef} type="submit"> 送信 </button> </form> </div> ) } export default App
まとめ
| やりたいこと | 使うもの | ポイント |
|---|---|---|
| 画面表示時に自動フォーカス | useRef + useEffect(fn, []) |
依存配列を空にして初回のみ実行 |
| Enterキーで次フィールドへ | onKeyDown + e.preventDefault() |
デフォルトのフォーム送信を止めてから移動 |
| 送信後にフォーカスをリセット | handleSubmit 内で .focus() |
setState の後に呼ぶ |
| 子コンポーネントに ref を渡す | forwardRef |
第1型引数に ref の型、第2引数に Props の型 |