イベントハンドラ

Part 3 · 画面に動きをつける

イベントハンドラ

onClick に渡すのは「関数」であって「実行結果」ではない

押されたら何かする。入力されたら何かする。 アプリを作るというのは、突き詰めればこの繰り返しです。

React では、それを onClickonChange といった props で書きます。渡すのは関数で、呼ぶタイミングは React が決めます。

Part 0 でやった「関数を値として渡す」が、そのまま実物になります。

渡すのは関数、括弧は付けない

onClick には「押されたときに呼んでほしい関数」を渡します。

// ○ 関数そのものを渡す
<Button onClick={reset}>リセット</Button>

// ✕ その場で実行され、結果が渡ってしまう
<Button onClick={reset()}>リセット</Button>

下の書き方をすると、画面が表示された瞬間に実行されます。 ボタンを押す前に動いてしまうわけです。 Part 0-2 でやった「括弧は今すぐ実行しろの合図」がそのまま当てはまります。

引数を渡したいときは包む

「押されたら setSelected("さとう") を呼びたい」 という場合、括弧を付けたら実行されてしまいます。 そこで、それを呼ぶだけの関数を新しく作って渡します。

onClick={() => setSelected("さとう")}
押したボタンによって表示を変える

引数のある処理は関数で包んで渡している

まだ選んでいません

map の中でボタンを作り、それぞれに違う値を渡しています。 この形は実際のアプリで山ほど出てきます。

click-basic.tsx

"use client";

import { Button } from "@/components/ui/button";
import { useState } from "react";

const members = ["さとう", "すずき", "たかはし"];

export function ClickBasic() {
  const [selected, setSelected] = useState("まだ選んでいません");

  // 引数のいらない関数は、そのまま渡す
  const reset = () => setSelected("まだ選んでいません");

  return (
    <div className="flex flex-col gap-4">
      <p className="rounded-md border p-3">{selected}</p>

      <div className="flex flex-wrap gap-2">
        {members.map((name) => (
          <Button
            key={name}
            size="sm"
            variant="outline"
            // 引数を渡したいときは、それを呼ぶ関数で包む
            onClick={() => setSelected(`${name} を選びました`)}
          >
            {name}
          </Button>
        ))}

        <Button size="sm" onClick={reset}>
          リセット
        </Button>
      </div>
    </div>
  );
}

何が起きたかを受け取る

ハンドラは、呼ばれるときに何が起きたのかを説明するオブジェクト を受け取ります。慣習的に evente と名付けます。

いちばんよく使うのは、入力欄の値を取り出す場面です。

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  setText(event.target.value);
};
  • event.target … 出来事が起きた要素そのもの(この場合は入力欄)
  • event.target.value … いま入力されている文字
入力された文字を受け取る

打つたびにハンドラが呼ばれている

まだ入力されていません

event-object.tsx

"use client";

import { Input } from "@/components/ui/input";
import { useState } from "react";

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

  // ハンドラは、何が起きたかを説明するオブジェクトを受け取る
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // event.target が入力欄そのもの。その value が今の文字
    setText(event.target.value);
  };

  return (
    <div className="flex flex-col gap-3">
      <Input placeholder="何か入力してみる" onChange={handleChange} />
      <p className="rounded-md border p-3">
        {text === "" ? "まだ入力されていません" : `${text.length} 文字: ${text}`}
      </p>
    </div>
  );
}

よく使うイベント

<button onClick={...}>       // 押された
<input onChange={...}>       // 入力が変わった
<form onSubmit={...}>        // 送信された
<input onFocus={...}>        // 入力欄が選ばれた
<input onBlur={...}>         // 入力欄から離れた
<div onMouseEnter={...}>     // マウスが乗った

いずれも on + 出来事 という名前で、 キャメルケースで書きます。JSX の章でやったとおり、 JavaScript の命名にそろえてあるためです。

既定の動きを止める

フォームの送信ボタンを押すと、ブラウザは既定でページを再読み込みします。 React で作ったアプリではたいてい困るので、これを止めます。

const handleSubmit = (event: React.FormEvent) => {
  event.preventDefault(); // 再読み込みを止める
  // ここで送信の処理を書く
};

click-basic.tsx

"use client";

import { Button } from "@/components/ui/button";
import { useState } from "react";

const members = ["さとう", "すずき", "たかはし"];

export function ClickBasic() {
  const [selected, setSelected] = useState("まだ選んでいません");

  // 引数のいらない関数は、そのまま渡す
  const reset = () => setSelected("まだ選んでいません");

  return (
    <div className="flex flex-col gap-4">
      <p className="rounded-md border p-3">{selected}</p>

      <div className="flex flex-wrap gap-2">
        {members.map((name) => (
          <Button
            key={name}
            size="sm"
            variant="outline"
            // 引数を渡したいときは、それを呼ぶ関数で包む
            onClick={() => setSelected(`${name} を選びました`)}
          >
            {name}
          </Button>
        ))}

        <Button size="sm" onClick={reset}>
          リセット
        </Button>
      </div>
    </div>
  );
}

理解できたか確かめる

確認クイズ

onClick={handleClick()} と書くとどうなる?

確認クイズ

入力欄の値を取り出すには?

click-basic.tsx

"use client";

import { Button } from "@/components/ui/button";
import { useState } from "react";

const members = ["さとう", "すずき", "たかはし"];

export function ClickBasic() {
  const [selected, setSelected] = useState("まだ選んでいません");

  // 引数のいらない関数は、そのまま渡す
  const reset = () => setSelected("まだ選んでいません");

  return (
    <div className="flex flex-col gap-4">
      <p className="rounded-md border p-3">{selected}</p>

      <div className="flex flex-wrap gap-2">
        {members.map((name) => (
          <Button
            key={name}
            size="sm"
            variant="outline"
            // 引数を渡したいときは、それを呼ぶ関数で包む
            onClick={() => setSelected(`${name} を選びました`)}
          >
            {name}
          </Button>
        ))}

        <Button size="sm" onClick={reset}>
          リセット
        </Button>
      </div>
    </div>
  );
}

この章のまとめ

  • onClick には関数を渡す。括弧を付けない
  • 引数が必要なときは () => f(引数) で包む
  • ハンドラは event を受け取る。 入力値は event.target.value
  • 名前は on + 出来事 のキャメルケース
  • フォームの再読み込みは event.preventDefault() で止める

click-basic.tsx

"use client";

import { Button } from "@/components/ui/button";
import { useState } from "react";

const members = ["さとう", "すずき", "たかはし"];

export function ClickBasic() {
  const [selected, setSelected] = useState("まだ選んでいません");

  // 引数のいらない関数は、そのまま渡す
  const reset = () => setSelected("まだ選んでいません");

  return (
    <div className="flex flex-col gap-4">
      <p className="rounded-md border p-3">{selected}</p>

      <div className="flex flex-wrap gap-2">
        {members.map((name) => (
          <Button
            key={name}
            size="sm"
            variant="outline"
            // 引数を渡したいときは、それを呼ぶ関数で包む
            onClick={() => setSelected(`${name} を選びました`)}
          >
            {name}
          </Button>
        ))}

        <Button size="sm" onClick={reset}>
          リセット
        </Button>
      </div>
    </div>
  );
}