1. ホーム
  2. React

【React】イベントハンドリングの使い方|onClick・onChange とイベントオブジェクトを解説

Share

ボタンを押したら処理を実行する、入力欄の文字を受け取る——こうした「ユーザーの操作に反応する」処理は、React では onClickonChange といった属性にイベントハンドラ(処理を書いた関数)を渡して実現します。素の HTML の onclick="..." とは書き方も渡すものも違い、React 独自の合成イベント(SyntheticEvent)という仕組みが関わります。この記事では、イベントハンドラの基本の書き方から、onChange で入力値を取り出す方法、ハンドラへの引数の渡し方、e.preventDefault() でのデフォルト動作の抑止、そして TypeScript でのイベント型注釈まで、初心者向けに順を追って解説します。

React でのイベントの書き方と HTML との違い

React では、JSX の要素にイベント名の属性を付け、そこに関数そのものを渡します。イベント名は onClickonChange のようにキャメルケース(先頭が小文字で単語の区切りを大文字にする書き方)で書きます。素の HTML では属性名がすべて小文字の onclick で、値は実行するコードを書いた「文字列」でしたが、React では属性名がキャメルケースで、値は {} の中に書いた「関数」だという点が大きく異なります。

Button.tsx
function Button() {
  // クリックされたときに実行される関数(イベントハンドラ)
  const handleClick = () => {
    alert("クリックされました");
  };

  // onClick に関数「そのもの」を渡す(呼び出さない)
  return <button onClick={handleClick}>送信</button>;
}

比較として、素の HTML と JavaScript では次のように書きます。属性名が小文字で、値が実行するコードの文字列になっている点に注目してください。

plain.html
<!-- 素の HTML:属性は小文字、値は実行するコードの文字列 -->
<button onclick="alert('クリックされました')">送信</button>

React で渡すのは「呼び出したい関数の参照」です。onClick={handleClick} と書くと、React はクリックが起きたときに handleClick を呼び出してくれます。ここで onClick={handleClick()} のように末尾に () を付けてしまうと意味が変わるので、この違いは後半の「つまずきポイント」で詳しく取り上げます。

合成イベント(SyntheticEvent)とは

イベントハンドラは、呼び出されるときにイベントオブジェクトを第1引数として受け取ります。React が渡すこのオブジェクトは SyntheticEvent(合成イベント)と呼ばれ、ブラウザのネイティブなイベントを React がラップしたものです。preventDefault()stopPropagation()targetcurrentTarget といった、ブラウザ標準のイベントと同じプロパティ・メソッドを持ちます。

合成イベントの利点は、ブラウザごとのイベントの差異を React が吸収し、どの環境でも同じインターフェースで扱える点にあります。慣例として、イベントオブジェクトの引数は eevent という名前で受け取ります。

ClickLogger.tsx
function ClickLogger() {
  const handleClick = (e) => {
    // e が合成イベント(SyntheticEvent)
    // currentTarget はイベントハンドラを付けた要素を指す
    console.log(e.type); // "click"
    console.log(e.currentTarget); // button 要素
  };

  return <button onClick={handleClick}>ログを出す</button>;
}

ネイティブなイベントオブジェクトが必要な場面では、e.nativeEvent からアクセスできます。ただし、日常的な処理のほとんどは合成イベントのプロパティだけで完結します。

onChange で入力値を取得する

テキスト入力欄などの値が変わったことを検知するには onChange を使います。入力された文字は、イベントオブジェクトの e.target.value から取得できます。target はイベントが発生した要素(この場合は input)を指し、その value が現在の入力値です。

SearchBox.tsx
import { useState } from "react";

function SearchBox() {
  const [keyword, setKeyword] = useState("");

  const handleChange = (e) => {
    // 入力欄の現在の値を取り出す
    setKeyword(e.target.value);
  };

  return (
    <div>
      <input value={keyword} onChange={handleChange} />
      <p>入力中: {keyword}</p>
    </div>
  );
}

ここでは e.target.value で取り出した文字を setKeyword で状態に反映しています。チェックボックスの場合は値ではなくオン・オフを扱うため、e.target.checkedtrue / false)を参照します。なお、React の onChange は素の HTML の change イベントとは挙動が異なり、キー入力のたびに発火します(HTML の change は入力確定時のみ)。1文字ごとに値を受け取れると考えてください。

イベントハンドラに引数を渡す

「どの項目が押されたか」など、イベントハンドラに独自の引数を渡したい場面があります。ここで onClick={handleDelete(id)} と書くとレンダー時に即実行されてしまうため、アロー関数でラップして渡すのが基本です。こうすると、クリックされた瞬間に初めて中の関数が呼ばれます。

ItemList.tsx
function ItemList({ items }) {
  const handleDelete = (id) => {
    console.log(`${id} を削除`);
  };

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
          {/* アロー関数で包み、クリック時に id を渡す */}
          <button onClick={() => handleDelete(item.id)}>削除</button>
        </li>
      ))}
    </ul>
  );
}

イベントオブジェクトも一緒に使いたいときは、アロー関数の引数に e を受け取り、それを中の関数へ渡します。onClick={(e) => handleDelete(item.id, e)} のように書けば、独自の引数と合成イベントの両方を扱えます。

preventDefault でデフォルト動作を止める

フォームの送信ボタンを押すと、ブラウザは既定の動作としてページ全体をリロード(再読み込み)しようとします。React で入力内容を JavaScript の側で処理したいときは、この既定動作が邪魔になります。そこで onSubmit のハンドラ内で e.preventDefault() を呼び、デフォルトのフォーム送信を止めます。

LoginForm.tsx
import { useState } from "react";

function LoginForm() {
  const [name, setName] = useState("");

  const handleSubmit = (e) => {
    // ページのリロードを止める
    e.preventDefault();
    console.log(`送信された名前: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button type="submit">送信</button>
    </form>
  );
}

送信の検知は、ボタンの onClick ではなく formonSubmit に付けるのがポイントです。こうすると、ボタンのクリックだけでなく、入力欄で Enter キーを押したときの送信もまとめて拾えます。e.preventDefault() はリンク(<a>)の遷移を止めたいときなど、フォーム以外の要素でも同じように使えます。

よく使うイベントの対応表

React で扱えるイベントは多数ありますが、まずは下の表にある基本的なものを押さえておけば十分です。属性名はいずれもキャメルケースで書きます。

属性発火するタイミング
onClick要素がクリックされたとき
onChange入力欄の値が変わったとき(React では入力のたび)
onSubmitフォームが送信されたとき
onKeyDownキーが押されたとき
onInput入力が行われたとき
onFocus要素がフォーカスを得たとき
onBlur要素がフォーカスを失ったとき
onMouseEnterマウスカーソルが要素に入ったとき

たとえば入力欄で Enter キーが押されたことを検知するには、onKeyDown のハンドラで e.key を確認します。if (e.key === "Enter") { ... } のように書けば、特定のキーだけに反応させられます。

TypeScript でのイベント型注釈

TypeScript で書く場合、イベントオブジェクトの引数 e に型を付けます。型は React.MouseEventReact.ChangeEvent のように、イベントの種類ごとに用意されています。多くは対象の要素の型を型引数(<...> の中身)で指定します。

TypedForm.tsx
import { useState } from "react";

function TypedForm() {
  const [text, setText] = useState("");

  // クリックイベント:対象はボタン要素
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log(e.currentTarget.textContent);
  };

  // 入力変更イベント:対象は input 要素
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };

  // フォーム送信イベント
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log(text);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={handleChange} />
      <button type="submit" onClick={handleClick}>送信</button>
    </form>
  );
}

型を付けておくと、e.target.valuee.currentTarget が正しく補完・型チェックされ、打ち間違いに気付きやすくなります。よく使うイベント型を下の表にまとめます。

主な用途
React.MouseEvent<HTMLButtonElement>onClick などボタンのマウス操作
React.ChangeEvent<HTMLInputElement>onChange での入力欄の変更
React.FormEvent<HTMLFormElement>onSubmit でのフォーム送信
React.KeyboardEvent<HTMLInputElement>onKeyDown などのキー操作

なお、ハンドラを JSX の属性内に直接アロー関数で書く場合(インラインハンドラ)は、React が引数の型を推論してくれるため、型注釈を省略できることが多いです。onChange={(e) => setText(e.target.value)} のように書けば、e は自動的に適切な型になります。

onClick に関数を渡すときのよくある間違い

イベントハンドラで最もつまずきやすいのが、onClick={handleClick}onClick={handleClick()} の違いです。前者は「関数そのもの」を渡していますが、後者は末尾に () が付いているため、レンダーされた瞬間に handleClick が実行され、その戻り値が onClick に渡ってしまいます

mistake.tsx
// NG:() を付けると、表示された瞬間に実行される
<button onClick={handleClick()}>送信</button>

// OK:関数そのものを渡す(クリック時に呼ばれる)
<button onClick={handleClick}>送信</button>

// 引数を渡したいときは、アロー関数で包む
<button onClick={() => handleClick(id)}>送信</button>

handleClick() の中で状態を更新している場合、レンダーのたびに実行されて再レンダーが起き、無限ループになることもあります。「クリックしていないのに処理が走る」「画面が固まる」といった症状が出たら、onClick に渡している関数に余計な () が付いていないかを疑ってください。引数を渡したい場合は、前述のとおりアロー関数で包むのが正解です。

まとめ

React のイベントハンドリングは、onClickonChange といったキャメルケースの属性に、実行したい関数そのものを渡すのが基本です。ハンドラは合成イベント(SyntheticEvent)を第1引数で受け取り、e.target.value で入力値を、e.preventDefault() でデフォルト動作の抑止を行えます。引数を渡したいときはアロー関数でラップし、TypeScript では React.MouseEventReact.ChangeEvent<HTMLInputElement> などの型を付けます。onClick={handleClick()} と括弧を付けると即実行されてしまう点にだけ注意すれば、基本的なユーザー操作はひととおり扱えるようになります。

参考ページ