dev React

React FormEvent 非推奨対応とイベント処理の基本|実例で解説

React FormEvent を使ったら「非推奨」と言われて困っていませんか?または React のイベント処理(クリック・入力・フォーム)の書き方に迷っていませんか?

この記事では、React FormEvent の現状preventDefault の正しい使い方、そして TypeScript で型を付けたイベント処理の書き方 まで、実例で解説します。

読み終わる頃には、自分のコードでイベント処理を自信を持って書き分けられるようになります。

結論:React FormEvent は今も使えるが、より良い書き方がある

短く先にお伝えすると:

  • React.FormEvent は今も動作するものの、新しい React の型定義では TypeScript から「非推奨」の警告 が出ます。
  • 代わりに React.SubmitEvent を使うのが推奨されています。event.preventDefault() などの動作はそのまま使えます。
  • 既存コードを書き換える場合も、型を FormEventSubmitEvent に置き換えるだけで済むケースがほとんどです。
// 旧(非推奨警告が出る)
const handleSubmit = (event: React.FormEvent) => {
  event.preventDefault()
  const formData = new FormData(event.target as HTMLFormElement)
  // ...
}

// 新(推奨)
const handleSubmit = (event: React.SubmitEvent) => {
  event.preventDefault()
  const formData = new FormData(event.target as HTMLFormElement)
  // ...
}

それでは、React のイベント処理の基本から、上記の置き換えに至るまでを順に詳しく見ていきましょう。

参照元

本記事は以下の Zenn 教材で React の基本を学んだうえで、自分なりに整理し直したものです。

イベント処理の章を中心に、TypeScript の型定義と組み合わせた書き方を実際にハンズオンしながら確認しました。

得られるもの

この記事を読むと、以下のことができるようになります。

  • React で クリック・入力・フォーム のイベントを TypeScript の型付きで書ける
  • React.MouseEvent / React.ChangeEvent / React.SubmitEvent などのイベント型を場面に応じて使い分けられる
  • event.targetevent.currentTarget の違いを理解して使い分けられる
  • preventDefault を使って、フォーム送信やリンク遷移のデフォルト動作を防止できる
  • React.FormEvent の非推奨化にどう対応するかがわかる

プロジェクトのフォルダ構成

ハンズオンで作成したファイルは以下のとおりです。

src/
├── App.css
├── App.tsx
├── assets/
├── ClickExample.tsx
├── EventExample.tsx
├── FormExample.tsx
├── InputExample.tsx
├── index.css
└── main.tsx

ClickExample.tsx / InputExample.tsx / EventExample.tsx / FormExample.tsx の 4 つのコンポーネントで、それぞれ別の種類のイベントを扱います。

前提条件

以下が完了している前提で進めます。

  • Vite + React + TypeScript プロジェクトの作成
  • Tailwind CSS の設定

セットアップがまだの方は、先に Vite + React + TypeScript の環境構築記事 を参照してください。

クリックイベントの基本

まずは一番馴染みのある クリックイベント から見ていきます。ClickExample.tsx を以下のように書きました。

const ClickExample = () => {
  const showMessage = (message: string) => {
    alert(message)
  }

  const showTime = () => {
    const now = new Date()
    alert(`現在の時刻: ${now.toLocaleTimeString()}`)
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">様々なクリックイベント</h2>

      <div className="space-y-4">
        <button
          onClick={() => showMessage('こんにちは!')}
          className="block bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600"
        >
          挨拶メッセージ
        </button>

        <button
          onClick={showTime}
          className="block bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600"
        >
          現在時刻を表示
        </button>
      </div>
    </div>
  )
}

export default ClickExample

ポイントは onClick の渡し方が 2 種類ある ことです。

  • 引数あり: onClick={() => showMessage('こんにちは!')} のようにアロー関数でラップする
  • 引数なし: onClick={showTime} のように関数名を直接渡す

引数を渡したい場合に onClick={showMessage('こんにちは!')} と書いてしまうと、レンダリング時に即座に実行されてしまうため注意してください。

ブラウザで表示すると、以下のように 2 つのボタンが表示されます。

React event handling クリックイベントのボタン表示画面

「挨拶メッセージ」ボタンをクリックすると、以下のように alert が表示されます。

React event handling 挨拶メッセージボタン押下時のalert表示

「現在時刻を表示」ボタンをクリックすると、現在時刻が alert に表示されます。

React event handling 現在時刻表示ボタン押下時のalert表示

クリックイベントの基本はこれだけです。次は入力フォームを扱います。

入力イベントの処理

次に 入力イベントonChange)を見ていきます。InputExample.tsx を以下のように書きました。

const InputExample = () => {
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log('入力された値:', event.target.value)
  }

  const handleSubmit = () => {
    const inputElement = document.getElementById('nameInput') as HTMLInputElement
    const inputValue = inputElement.value

    if (inputValue.trim() === '') {
      alert('名前を入力してください')
    } else {
      alert(`こんにちは、${inputValue}さん!`)
    }
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">入力イベントの例</h2>

      <div className="max-w-md">
        <label className="block text-sm font-medium mb-2">
          お名前を入力してください
        </label>

        <input
          id="nameInput"
          type="text"
          onChange={handleInputChange}
          placeholder="名前を入力"
          className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500"
        />

        <button
          onClick={handleSubmit}
          className="mt-4 bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600"
        >
          送信
        </button>
      </div>
    </div>
  )
}

export default InputExample

ブラウザで表示すると、入力フォームと送信ボタンが表示されます。

React event handling 入力イベントのフォーム表示画面

何も入力せず送信ボタンを押すと、バリデーションが働きます。

React event handling 名前未入力時のバリデーションalert表示

名前を入力して送信すると、挨拶メッセージが表示されます。

React event handling 名前入力後の送信結果alert表示

onChange イベントは入力のたびに発火するため、コンソールには入力中の値が逐次出力されます。

React event handling onChangeイベントでコンソールに入力値が出力される様子

イベントの型について

入力イベントのハンドラには、TypeScript で以下の型を指定しています。

(event: React.ChangeEvent<HTMLInputElement>) => { ... }

React.ChangeEvent<HTMLInputElement> は、<input> 要素で発生した change イベント」 を表す型です。event.target.value のような書き方が型エラーにならないのは、この型指定によって targetHTMLInputElement だと TypeScript が認識できているためです。

C# 経験者向けに補足すると、EventHandler<TEventArgs>TEventArgs を指定しているのと同じイメージです。React 側ではジェネリクスで対象の HTML 要素を渡す形になっています。

document.getElementById は React 的にアンチパターン

このサンプルでは handleSubmit 内で document.getElementById('nameInput') を使って入力値を取得していますが、これは React では推奨されない書き方 です。

Reactでは、DOM直接操作を避けて状態(state)で管理するのが基本的な設計思想です。

正しくは useState で入力値を管理し、ボタンクリック時にその state を読むべきです。今回は「onChange がどう動くか」を見るために素朴に書いていますが、実際のアプリでは useState を使ったフォーム管理 に置き換えることを意識してください。

イベントオブジェクトの型定義

React のイベント処理では、React.○○Event<HTML要素> というパターンの型を場面に応じて使い分けます。EventExample.tsx で複数のイベント型をまとめて確認しました。

const EventExample = () => {
  const handleMouseEvent = (event: React.MouseEvent<HTMLDivElement>) => {
    console.log('マウスの位置:', event.clientX, event.clientY)
    console.log('クリックされた要素:', event.currentTarget.tagName)
  }

  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
    console.log('押されたキー:', event.key)
    if (event.key === 'Enter') {
      alert('Enterキーが押されました!')
    }
  }

  const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.backgroundColor = 'lightyellow'
  }

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.backgroundColor = 'white'
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">イベントオブジェクトの例</h2>

      <div className="space-y-4">
        <div>
          <button
            onClick={handleMouseEvent}
            className="bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600"
          >
            クリック位置を表示(コンソールを確認)
          </button>
        </div>

        <div>
          <input
            type="text"
            onKeyDown={handleKeyPress}
            placeholder="何か入力してください(Enterキーを試してみて)"
            className="w-full max-w-md px-3 py-2 border border-gray-300 rounded-lg"
          />
        </div>

        <div>
          <input
            type="text"
            onFocus={handleFocus}
            onBlur={handleBlur}
            placeholder="フォーカス時に背景色が変わります"
            className="w-full max-w-md px-3 py-2 border border-gray-300 rounded-lg"
          />
        </div>
      </div>
    </div>
  )
}

export default EventExample

ブラウザで表示すると、ボタンと 2 つの input が並びます。

React event handling イベントオブジェクトの各種イベント確認画面

ボタンをクリックすると、コンソールにマウスの座標と要素名が出力されます。

React event handling マウスイベントのコンソール出力でマウス座標と要素名を表示

input に文字を入力して Enter を押すと、alert が出ます。

React event handling Enterキー押下時のalert表示

下の input にフォーカスすると背景色が変わります。

React event handling フォーカス時に背景色がlightyellowに変わる様子

イベント型の一覧

ここまでに登場したイベントを表でまとめると、以下のようになります。

イベントハンドラ主な用途
onChangeReact.ChangeEventinput / select / textarea の値変更
onClick / onMouseMoveReact.MouseEventクリック・マウス操作
onKeyDown / onKeyPressReact.KeyboardEventキーボード操作
onFocus / onBlurReact.FocusEventフォーカス・ブラー
onSubmitReact.SubmitEventフォーム送信(旧 React.FormEvent)

ジェネリクスで対象の HTML 要素を指定すると、event.targetevent.currentTarget の型が確定して補完が効くようになります。

target と currentTarget の違い

event オブジェクトには targetcurrentTarget の 2 つのプロパティがあり、混同しやすい部分です。

  • event.target: 実際にイベントが発生した要素(子要素の可能性あり)
  • event.currentTarget: イベントハンドラが設定された要素自体

たとえば <button> の中に <span> があり、<span> をクリックした場合、

  • event.target<span>
  • event.currentTarget<button>

となります。多くの場合は currentTarget を使うほうが安全です。

target を DOM 要素として扱いたい場合は、型アサーションが必要になります。

(event.target as HTMLElement).tagName

FormEvent の非推奨について

最後に、最近よく検索されている React.FormEvent の話題です。

冒頭の結論でもお伝えした通り、React.FormEvent は今も動作しますが、新しい React の型定義(@types/react)では非推奨マークが付いています

代わりに React.SubmitEvent を使うのが推奨されています。実際のサンプルコードは次のセクションで確認します。

デフォルト動作の防止(preventDefault)

最後に、フォーム送信やリンククリックの デフォルト動作を防止する preventDefault を扱います。FormExample.tsx は以下のとおりです。

const FormExample = () => {
  const handleSubmit = (event: React.SubmitEvent) => {
    event.preventDefault()

    const formData = new FormData(event.target as HTMLFormElement)
    const name = formData.get('name')
    const email = formData.get('email')

    alert(`フォーム送信処理\n名前: ${name}\nメール: ${email}`)
  }

  const handleLinkClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
    event.preventDefault()
    alert('リンクのデフォルト動作を防ぎました')
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">preventDefault()の例</h2>

      <div className="space-y-8">
        <div>
          <h3 className="text-lg font-semibold mb-4">フォーム送信の例</h3>
          <form onSubmit={handleSubmit} className="max-w-md space-y-4">
            <div>
              <label className="block text-sm font-medium mb-1">名前</label>
              <input
                name="name"
                type="text"
                required
                className="w-full px-3 py-2 border border-gray-300 rounded-lg"
              />
            </div>

            <div>
              <label className="block text-sm font-medium mb-1">メールアドレス</label>
              <input
                name="email"
                type="email"
                required
                className="w-full px-3 py-2 border border-gray-300 rounded-lg"
              />
            </div>

            <button
              type="submit"
              className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600"
            >
              送信(ページはリロードされません)
            </button>
          </form>
        </div>

        <div>
          <h3 className="text-lg font-semibold mb-4">リンクの例</h3>
          <a
            href="https://example.com"
            onClick={handleLinkClick}
            className="text-blue-500 underline hover:text-blue-700"
          >
            このリンクをクリックしても移動しません
          </a>
        </div>
      </div>
    </div>
  )
}

export default FormExample

ポイントは以下の 3 点です。

  1. <form onSubmit={handleSubmit}> のハンドラ型は React.SubmitEvent を使う(React.FormEvent ではない)
  2. event.preventDefault() でフォームの デフォルトの送信動作(ページリロード) を防止する
  3. <a> タグのクリックでも event.preventDefault()リンク遷移を防止 できる

ブラウザで表示すると、フォームとリンクが並びます。

React event handling フォーム送信のフォーム表示画面

名前とメールアドレスを入力して送信ボタンを押すと、ページリロードされずに alert が表示されます。

React event handling フォーム送信時にpreventDefaultでページリロードを防止した結果

リンクをクリックしても外部サイトに飛ばずに alert が表示されます。

React event handling リンククリック時にpreventDefaultで遷移を防止した結果

preventDefault は SPA でページ遷移を制御する場面でも頻出するので、ここでしっかり押さえておきましょう。

まとめ

React のイベント処理は、TypeScript の型を組み合わせて使うことで安全に書けます。

  • クリック・入力・フォーム のイベントは React.MouseEvent / React.ChangeEvent / React.SubmitEvent を使い分ける
  • event.targetevent.currentTarget の違いを理解しておく
  • React.FormEvent は非推奨、代わりに React.SubmitEvent を使う
  • preventDefault でフォーム送信やリンク遷移のデフォルト動作を防止できる

C# の EventHandler<TEventArgs> と同様、React.○○Event<HTML要素> というパターンで対応できると押さえれば、新しいイベント型に出会っても怖くありません。

これまでの学習内容

React 入門シリーズの関連記事です。順番に読むと体系的にキャッチアップできます。

また、React と一緒に使う TailwindCSS の逆引きガイドもあります。

それではまた、別の記事でお会いしましょう。

-dev, React