Chapter 05

イベントハンドリング

クリック・入力・送信などのユーザー操作に反応する方法を学びます。インタラクティブなUIを作るための基礎です。

40 min

このチャプターで学ぶこと

  • onClick、onChange、onSubmitの基本的な使い方を理解できる
  • イベントハンドラ関数とインラインアロー関数の使い分けができる
  • イベントオブジェクトから値を取得できる(e.target.value)
  • e.preventDefault()でデフォルト動作を防げる
  • ハンドラに引数を渡せる
  • テキストの表示・非表示を切り替えるトグルボタンを作れる

イベントハンドリング

前のチャプターで onClick を少し使いました。このチャプターではイベントハンドリングをしっかり学んで、ユーザーの操作に反応するUIを作れるようになりましょう。

イベントハンドラの基本

Reactでのイベントハンドリングは HTMLの onclick に似ていますが、いくつか違いがあります。

<!-- HTML の書き方 -->
<button onclick="handleClick()">クリック</button>
// React の書き方
<button onClick={handleClick}>クリック</button>

主な違い:

⚠️ () を付けないように注意

よくあるミスが onClick={handleClick()} と書いてしまうことです。

// NG: レンダリング時に即実行されてしまう
<button onClick={handleClick()}>クリック</button>

// OK: クリック時に実行される
<button onClick={handleClick}>クリック</button>

handleClick() と書くと、ボタンがレンダリングされた瞬間に関数が実行されてしまいます。handleClick(カッコなし)で関数への参照を渡しましょう。

イベントハンドラ関数 vs インラインアロー関数

イベントハンドラの書き方は2種類あります。

1. 別に関数を定義する(推奨)

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  // コンポーネントの内部で関数を定義
  function handleIncrement() {
    setCount(count + 1);
  }

  function handleDecrement() {
    setCount(count - 1);
  }

  function handleReset() {
    setCount(0);
  }

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={handleDecrement}>-</button>
      <button onClick={handleReset}>リセット</button>
      <button onClick={handleIncrement}>+</button>
    </div>
  );
}

2. インラインアロー関数

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>カウント: {count}</p>
      {/* JSX の中に直接書く */}
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(0)}>リセット</button>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}
💡 どちらを使うべきか

シンプルな処理ならインラインアローでOKです。複数行になるような処理や、同じハンドラを複数の場所で使う場合は別に関数を定義したほうが読みやすくなります。

慣習として、イベントハンドラ関数の名前は handle で始めることが多いです(例:handleClickhandleSubmithandleChange)。

onClick:クリックイベント

最もよく使うイベントです。

import { useState } from 'react';

function Greeting() {
  const [message, setMessage] = useState('ボタンを押してください');

  function handleClick() {
    setMessage('こんにちは!');
  }

  return (
    <div>
      <p>{message}</p>
      <button onClick={handleClick}>挨拶する</button>
    </div>
  );
}

onChange:入力値の変化

テキスト入力、セレクトボックス、チェックボックスなど、値が変化したときに発火します。

import { useState } from 'react';

function NameInput() {
  const [name, setName] = useState('');

  function handleChange(e) {
    // e.target.value で入力されたテキストを取得
    setName(e.target.value);
  }

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={handleChange}
        placeholder="名前を入力"
      />
      {/* 入力するたびにリアルタイムで反映される */}
      <p>こんにちは、{name || 'ゲスト'} さん!</p>
    </div>
  );
}

イベントオブジェクト e

ハンドラ関数はイベントオブジェクト e(または event)を受け取ります。その中に有用な情報が入っています。

function handleChange(e) {
  console.log(e.target);        // イベントが発生した要素(input要素)
  console.log(e.target.value);  // 入力された値
  console.log(e.target.name);   // input の name 属性
  console.log(e.target.checked); // チェックボックスの場合
  console.log(e.type);          // イベントの種類("change")
}
📝 バックエンドの$_POSTに似てる

e.target.value でフォームの値を取得するのは、PHPで $_POST['field_name'] を使う感覚に似ています。ただしReactでは送信まで待たず、入力のたびにリアルタイムで取得できます。

チェックボックスとセレクトボックス

import { useState } from 'react';

function FormExample() {
  const [isAgreed, setIsAgreed] = useState(false);
  const [color, setColor] = useState('red');

  return (
    <div>
      {/* チェックボックス: checked と e.target.checked を使う */}
      <label>
        <input
          type="checkbox"
          checked={isAgreed}
          onChange={(e) => setIsAgreed(e.target.checked)}
        />
        規約に同意する
      </label>
      <p>同意状態: {isAgreed ? 'はい' : 'いいえ'}</p>

      {/* セレクト: value と e.target.value を使う */}
      <select value={color} onChange={(e) => setColor(e.target.value)}>
        <option value="red"></option>
        <option value="blue"></option>
        <option value="green"></option>
      </select>
      <p>選択した色: {color}</p>
    </div>
  );
}

onSubmit:フォーム送信

フォームの送信を処理します。e.preventDefault() でブラウザのデフォルト動作(ページリロード)を防ぐのが重要です。

import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [message, setMessage] = useState('');

  function handleSubmit(e) {
    // デフォルト動作(ページリロード)を防ぐ
    e.preventDefault();

    // バリデーション
    if (!email || !password) {
      setMessage('メールアドレスとパスワードを入力してください');
      return;
    }

    // ここで API 呼び出しなどを行う
    setMessage(`${email} でログインしました`);
  }

  return (
    // form タグに onSubmit を付ける
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="メールアドレス"
        />
      </div>
      <div>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="パスワード"
        />
      </div>
      {/* type="submit" ボタンでフォームが送信される */}
      <button type="submit">ログイン</button>
      {message && <p>{message}</p>}
    </form>
  );
}
⚠️ e.preventDefault() を忘れずに

onSubmite.preventDefault() を呼ばないと、フォーム送信時にページがリロードされてしまいます。Laravel の @csrf トークンを付けてフォームを送信するのと違い、React ではデフォルトの送信を止めて自分でデータを処理します。

ハンドラに引数を渡す

リストのアイテムを削除するときなど、どのアイテムに対する操作かをハンドラに伝える必要があることがあります。

import { useState } from 'react';

function FruitList() {
  const [fruits] = useState(['りんご', 'バナナ', 'みかん']);
  const [selected, setSelected] = useState('');

  // どのフルーツが選ばれたかを受け取る関数
  function handleSelect(fruitName) {
    setSelected(fruitName);
  }

  return (
    <div>
      <p>選択中: {selected || 'なし'}</p>
      {fruits.map((fruit) => (
        <button
          key={fruit}
          // インラインアローで引数を渡す
          onClick={() => handleSelect(fruit)}
        >
          {fruit}
        </button>
      ))}
    </div>
  );
}

onClick={() => handleSelect(fruit)} のようにインラインアロー関数を使って引数を渡すのがよくあるパターンです。

💡 イベントオブジェクトも受け取りたいとき

引数を渡しつつイベントオブジェクトも受け取りたい場合は、こうします。

function handleClick(id, e) {
  e.preventDefault();
  console.log('IDは:', id);
}

// 呼び出し側
<button onClick={(e) => handleClick(item.id, e)}>削除</button>

よく使うその他のイベント

// マウスオーバー・マウスアウト
<div
  onMouseEnter={() => setIsHovered(true)}
  onMouseLeave={() => setIsHovered(false)}
>
  ホバーしてみて
</div>

// キーボード入力
<input
  onKeyDown={(e) => {
    // Enter キーが押されたとき
    if (e.key === 'Enter') {
      handleSubmit();
    }
  }}
/>

// フォーカス・ブラー
<input
  onFocus={() => console.log('フォーカスされた')}
  onBlur={() => console.log('フォーカスが外れた')}
/>
✍ やってみよう

演習: トグルボタンを作ろう

クリックするとテキストの表示・非表示が切り替わるトグルボタンを作りましょう。

localhost:5173
トグルボタンのデモ
ボタンを押すとテキストが現れたり消えたりする

要件:

  • 「表示する」ボタンをクリックするとテキストが表示され、ボタンのラベルが「隠す」に変わる
  • もう一度クリックするとテキストが消え、ボタンのラベルが「表示する」に戻る
  • 表示するテキストはなんでもOK(例:「秘密のメッセージ」)

ヒント:

import { useState } from 'react';

function ToggleText() {
  // isVisible を State で管理しよう
  const [isVisible, setIsVisible] = useState(false);

  function handleToggle() {
    // 現在の逆の値にする
    setIsVisible(/* ここを埋めよう */);
  }

  return (
    <div>
      <button onClick={handleToggle}>
        {/* isVisible の値でラベルを切り替えよう */}
        {isVisible ? '隠す' : '表示する'}
      </button>
      {/* isVisible が true のときだけ表示しよう */}
      {isVisible && <p>秘密のメッセージ: Reactは最高!</p>}
    </div>
  );
}

チャレンジ:

  • テキストフィールドを追加して、表示するメッセージをユーザーが入力できるようにしよう
  • テキストが空のときはボタンをクリックしても表示されないようにしよう(バリデーション)

まとめ

次のチャプターでは、リストのレンダリングと条件分岐を学びます。配列の .map() を使って動的なリストを作りましょう。