React Hooks入門 - useStateとuseEffectでタイマーを作る

初級 | 20分 で読める | 2024.12.15

公式ドキュメント

今回やること

ViteでReactプロジェクトを作り、開始・停止・リセットができるタイマーを実装します。

  • useStateで秒数と実行状態を持つ
  • クリックイベントでstateを更新する
  • stateから表示用の派生値を計算する
  • useEffectでintervalを開始し、cleanupで停止する

Context、Reducer、データ取得、最適化用Hooksは扱いません。最初に必要な2つのHooksへ集中します。

前提条件

  • Node.js 20.19以上、または22.12以上
  • npm
  • Reactのコンポーネントとpropsが分かる

Viteプロジェクトを作る

npm create vite@latest react-hooks-timer -- --template react-ts
cd react-hooks-timer
npm install
npm run dev

ターミナルに表示されたURLを開き、Viteの初期画面が表示されることを確認します。

タイマーを実装する

src/App.tsxを次の内容にします。

import { useEffect, useState } from "react";

export default function App() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) {
      return;
    }

    const intervalId = window.setInterval(() => {
      setSeconds((current) => current + 1);
    }, 1_000);

    return () => {
      window.clearInterval(intervalId);
    };
  }, [isRunning]);

  const status = isRunning ? "計測中" : "停止中";
  const isLongSession = seconds >= 10;

  function handleReset() {
    setIsRunning(false);
    setSeconds(0);
  }

  return (
    <main>
      <h1>学習タイマー</h1>
      <p aria-live="polite">経過時間: {seconds}</p>
      <p>状態: {status}</p>
      {isLongSession && <p>10秒続きました!</p>}

      <button type="button" onClick={() => setIsRunning(true)}>
        開始
      </button>
      <button type="button" onClick={() => setIsRunning(false)}>
        停止
      </button>
      <button type="button" onClick={handleReset}>
        リセット
      </button>
    </main>
  );
}

コードの要点

stateとイベント

useStateは現在の値と更新関数を返します。ボタンのonClickは更新関数を呼び、Reactへ再表示を依頼します。

const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);

interval内ではsetSeconds((current) => current + 1)という更新用関数を使います。古いsecondsを閉じ込めず、直前の値から安全に1を足せます。

派生値はrender中に計算する

statusisLongSessionは、既存のstateから計算できます。別のstateやEffectへ複製する必要はありません。

const status = isRunning ? "計測中" : "停止中";
const isLongSession = seconds >= 10;

stateを必要最小限にすると、値同士のずれを防げます。

Effectとcleanup

intervalはReactの外側で時間を刻む仕組みなので、useEffectで同期します。isRunningが変わる前とコンポーネントが画面から消える時に、cleanupが古いintervalを停止します。

Viteの初期構成は開発時にStrict Modeを使います。Strict Modeでは問題を見つけるため、Effectのsetup → cleanup → setupが追加で実行されます。このコードはsetupごとに作ったintervalIdを対応するcleanupで必ず停止するため、intervalが重複しません。

成功を確認する

開発serverが止まっている場合は起動します。

npm run dev

ブラウザで次を確認してください。

  1. 初期表示が0秒・停止中になる
  2. 「開始」を押すと約1秒ごとに数値が増える
  3. 「停止」を押すと数値が増えなくなる
  4. 10秒になるとメッセージが表示される
  5. 「リセット」で0秒・停止中へ戻る

約1秒で2ずつ増える場合は、cleanupが消えていないか確認します。

よくあるエラー

数値が増え続けて停止できない

Effectからwindow.clearInterval(intervalId)を行うcleanupを返しているか確認します。

1秒ごとにintervalが作り直される

Effect内でsetSeconds(seconds + 1)と書き、依存配列へsecondsを入れている可能性があります。更新用関数setSeconds((current) => current + 1)を使い、依存値をisRunningだけにします。

Hookを条件分岐の中で呼んでいる

useStateuseEffectはコンポーネントの最上位で呼びます。条件分岐はEffectの内側へ置きます。

練習

「+10秒」ボタンを追加し、setSeconds((current) => current + 10)で秒数を増やしてください。タイマーの実行中と停止中の両方で動くことを確認します。

次のステップ

タイマーをテストしたくなったら、まずReact Testing Library実践で利用者操作を基準にしたテストを学んでください。

参考リソース

(最終確認: 2026年7月25日)

← 一覧に戻る
PR
PR
PR
PR