Reactは、onClick、onChange、onSubmit のように、イベント名をキャメルケースにしたpropsでイベントを扱います。関数を渡すと、ユーザーがクリック、入力、送信したときに、Reactがイベントオブジェクトを渡してそれを呼びます。
クリックするたびに handleClick が実行され、カウントを更新し、プレビューの下のコンソールにログを出します。onClick の代わりに onDoubleClick を試してみてください。今度はダブルクリックだけがカウントされます。
関数は呼ばずに渡す
propは関数を受け取り、Reactがそれを後で呼びます。非常によくある間違いはかっこを付けることです。そうすると関数はすぐに、つまりレンダリング中に呼ばれ、Reactには代わりにその戻り値が渡されます。
何も触っていないのに、コンソールには「Hi, during render」と表示されます。Reactは最初のボタンをレンダリングする間に sayHi を実行し、sayHi は undefined を返すので、そのボタンにはハンドラがまったくありません。Wrongをクリックしても何も起きません。Rightをクリックすると「Hi, Ada」と記録されます。アロー関数はクリックしたときにだけ実行されるからです。
引数を渡すのにもアロー関数を使います。onClick={() => removeItem(item.id)} は項目ごとに小さな関数を作り、その関数が正しいIDで自分の関数を呼びます。ハンドラが引数を取らないなら、名前で渡します:onClick={handleClick}。
stateのセッターだと、この間違いはもっと深刻です。onClick={setCount(count + 1)} はレンダリング中にstateをセットし、それがもう一度レンダリングを起こし、またstateをセットし、Reactは「Too many re-renders」で止まります。
イベントオブジェクト
Reactはすべてのハンドラに、イベントオブジェクトという1つの引数を渡します。ブラウザのイベントと同じプロパティを持っています。e.target はイベントが起きた要素、e.key は押されたキー、e.preventDefault() はブラウザのデフォルトの動作を取り消します。Reactはネイティブイベントを合成イベントと呼ばれるもので包むので、どのブラウザでも同じように振る舞います。必要なら、元のイベントは e.nativeEvent にあります。
最もよく使うのは、onChange での e.target.value と、onKeyDown での e.key の2つです。
hooks と入力してEnterを押すと、タグが追加され、入力欄がクリアされます。コンソールには、Shift、Backspace、Enter も含め、すべてのキーが e.key の名前で記録されます。単語の途中でEscapeを押すと、その単語を捨てられます。
ReactのonChangeはキーを押すたびに発生します。素のHTMLでは、テキスト欄の change イベントはフォーカスを失ったときにだけ発生するので、これは意図的な違いです。これにより、入力欄とstateが1文字ずつ同期します。チェックボックスでは value の代わりに e.target.checked を使います。すべての入力の種類はフォームのページで扱っています。
フォームとpreventDefault
フォームが送信されると、ブラウザのデフォルトの動作はそれを送って新しいページを読み込むことです。Reactアプリではほとんどの場合データを自分で扱いたいので、送信ハンドラは e.preventDefault() で始めます。
ボタンの onClick ではなく、<form> に onSubmit を付けてください。そうすれば入力欄でEnterを押しても送信され、ブラウザ自身のチェック(ここでは required と type="email")が先に実行されます。e.preventDefault() の行を削除してもう一度送信してみてください。本物のページなら新しいページへ移動し、お礼のメッセージは失われます。プレビューはそれを防ぎ、代わりにフォームが送る内容を表示します。値のない email= です。フォームが読み取られた時点で、setEmail('') がすでに入力欄をクリアしていたからです。
React 19では <form action={fn}> に関数を渡すこともできます。その関数はフォームのデータを受け取り、非制御の入力欄をリセットしてくれます。フォームのページでは両方のスタイルを紹介しています。
バブリングとstopPropagation
ほとんどのイベントはバブリングします。クリックした要素で実行された後、その親で、さらにその親の親で、と一番上まで実行されます。Reactのハンドラも同じ順番に従います。クリックできるカードの中のボタンをクリックすると、カードのクリックとしても数えられるのはそのためです。
Bubbles upをクリックすると、コンソールにはボタン、内側のパネル、外側のカードの順に記録されます。Stops hereをクリックするとボタンだけが記録されます。e.stopPropagation() が上への伝わりを止めるのです。内側のパネル自身のテキストをクリックすると、パネルとカードが記録されます。
stopPropagation は控えめに使ってください。正当な使い方としてよくあるのは、クリックできるリストの行の中にある削除ボタンで、削除をクリックしたときに行も開かないようにする場合です。クリックが子から来たかどうかを親が知る必要があるなら、イベントを止めるのではなく e.target を確認してください。子がイベントを受け取る前に、下りの途中でハンドラを実行したいなら、Reactには onClickCapture のようなキャプチャ版があります。
preventDefault と stopPropagation は別の道具です。前者はブラウザがすること(リンクをたどる、フォームを送信する、チェックボックスにチェックを入れる)を取り消します。後者は、ほかのハンドラがそのイベントを受け取らないようにします。一方を呼んでも、もう一方の効果はありません。
そのほかのよく使うイベント
Reactがサポートするすべてのイベントで、同じパターンが使えます。よく使うものをいくつか挙げます。
onFocusとonBlurは入力欄がフォーカスを得たとき、失ったときに発生します。ユーザーが入力欄を離れた後にバリデーションを表示するのに便利です。onMouseEnterとonMouseLeaveはポインターが要素の上に乗ったとき、離れたときに発生します。ほとんどのイベントと違ってバブリングしないので、ポインターを親から子へ動かしても親のonMouseEnterはもう一度発生しません。onPointerDown、onPointerMove、onPointerUpはマウス、タッチ、ペンのどれでも動くので、ドラッグにはこちらのほうが適しています。onScrollは要素がスクロールすると発生します。Reactではバブリングしません。
各ハンドラは、それに合ったプロパティを持つイベントオブジェクトを受け取ります。ポインターイベントなら e.clientX、キーボードイベントなら e.key、そしてすべてのイベントで e.target です。
ハンドラをpropsとして渡す
コンポーネントは、ほかのpropと同じように親からハンドラを受け取れます。起きたことに合わせて onSomething と名付け、それを処理する関数は handleSomething と名付けます。組み込みのイベントにならった命名で、どのpropsがコールバックかがひと目でわかります。
Stepper は数値を持っていません。クリックを知らせ、その意味は親が決めます。handleDecrement を 0 ではなく 1 で止まるように変えると、Stepperは編集しなくてもそれに従います。stateを親に置いてハンドラを下へ渡すのは、stateのリフトアップで説明しているパターンです。
自作のハンドラpropは、ただの名前です。自分のコンポーネントの onIncrement は、コンポーネントがそれを本物の要素の onClick に渡すまで何もしません。また、onSelect(item.id) のように好きな引数を渡せます。
よくある質問
onClickハンドラに引数を渡すには?
呼び出しをアロー関数で包みます:onClick={() => deleteItem(item.id)}。クリック時にReactが呼ぶのはそのアロー関数で、それが引数を付けて自分の関数を呼びます。
ページを読み込んだときにonClickが実行されるのはなぜですか?
関数を渡さずに呼んでしまったからです。onClick={handleClick()} はレンダリング中に handleClick を実行し、その戻り値を渡します。onClick={handleClick} または onClick={() => handleClick(id)} と書いてください。
ReactのonChangeとHTMLのonChangeの違いは何ですか?
HTMLでは、テキスト入力の change イベントは入力欄がフォーカスを失ったときに発生します。ReactのonChangeはネイティブの input イベントのようにキーを押すたびに発生するので、stateが入力中のテキストに追従できます。
Reactでフォームがページを再読み込みしないようにするには?
onSubmit ハンドラの最初で e.preventDefault() を呼びます。ハンドラは送信ボタンではなく <form> に付けてください。そうすれば入力欄でEnterを押しても送信されます。
Reactの合成イベントとは何ですか?
Reactがハンドラに渡すイベントオブジェクトです。ブラウザのネイティブイベントを同じプロパティとメソッド(target、key、preventDefault()、stopPropagation())で包み、どのブラウザでも同じように振る舞います。元のイベントは e.nativeEvent で参照できます。