📖

Reactの状態管理:Stateはどこに置く?切り替えUIで答えが出た日

に公開

📘 はじめに

超初心者のReact + TypeScript 学習の記録です。
普段医療従事者をしておりますので、
慣れたカルテやアセスメント記録を題材に、学習を試みております。
今回は、useState を使った状態管理の基礎に取り組みました。
題材は「患者情報をボタンで切り替えるUI」です。

AIと壁打ちしながら実装 → エラー → 修正 → 設計改善 という流れを、そのまま学習記録としてまとめます。

初心者がどこで詰まり、どう考えればよいか
未来の自分のためのログです。

💡 背景・目的

前回までの状態

  • 患者情報が 3人同時に表示
  • 画面は静的(変化しない)

前回はこちら → 【React × TypeScript】型定義とPropsで患者カードを分離してみた

今回のゴール

  • 患者は 1人だけ表示
  • 「次の患者」「前の患者」ボタンで切り替え
  • 田中 → 佐藤 → 鈴木 → 田中(ループ)

技術スタック

  • Next.js(App Router / Client Component)
  • React(useState)
  • TypeScript
  • Tailwind CSS

⚙️ 実装の考え方(重要)

管理すべきStateは1つだけ

const [currentIndex, setCurrentIndex] = useState(0);
  • State:今どの患者を表示しているか(インデックス)
  • 表示:patients[currentIndex]

次・前に進むロジック

// 次
(currentIndex + 1) % patients.length

// 前(負の数対策が必要)
(currentIndex - 1 + patients.length) % patients.length

%(モジュロ演算子)は、
負の数をそのまま使うとバグる ことをここで学びました。

🧩 つまずいたポイント①

「handleNext is not defined」

原因はこれ👇

<button onClick={handleNext}></button>
  • 関数を 呼び出していない
  • そもそも 定義していなかった

👉 解決策
「クリック時に State を更新する関数を定義する」

function handleNext() {
  setCurrentIndex((currentIndex + 1) % patients.length);
}

🧩 つまずいたポイント②

「ボタンをコンポーネントに分けたいけど State が分からない」

最初にやってしまった構造👇

function Home() {
  const [currentIndex, setCurrentIndex] = useState(0);

  function Button() {
    // ← コンポーネントの中にコンポーネント
  }
}

問題点

  • 再レンダリングのたびに関数が再生成される
  • 実務では避けられる書き方

✅ 正解アプローチ

「Stateは親、操作はPropsで渡す」

親:app/page.tsx

"use client";

import { useState } from "react";
import PatientCard from "./components/PatientCard";
import NavigationButtons from "./components/NavigationButtons";
import { patients } from "./data/patients";

export default function Home() {
  const [currentIndex, setCurrentIndex] = useState(0);

  function handleNext() {
    setCurrentIndex((currentIndex + 1) % patients.length);
  }
  
  function handlePrev() {
    setCurrentIndex(
      (currentIndex - 1 + patients.length) % patients.length
    );
  }
  
  return (
    <main>
      <h1>患者一覧</h1>
      <h2>
        患者 {currentIndex + 1} / {patients.length}
      </h2>

      <PatientCard patient={patients[currentIndex]} />
      <NavigationButtons onNext={handleNext} onPrev={handlePrev} />
    </main>
  );
}

子:NavigationButtons.tsx

type NavigationButtonsProps = {
  onNext: () => void;
  onPrev: () => void;
};

export default function NavigationButtons({
  onNext,
  onPrev,
}: NavigationButtonsProps) {
  return (
    <div className="flex gap-4 mt-4">
      <button onClick={onPrev}>前の患者</button>
      <button onClick={onNext}>次の患者</button>
    </div>
  );
}

🎨 UI / UXの工夫

  • ボタンは 横並び(前 ↔ 次 の直感性)
  • hoverで色を変えて「押せる感」を出す
  • 「患者 1 / 3」で現在地を明示

🧠 学びまとめ

  • useStateは 「画面を変えたい値」だけを持つ
  • Stateは 一番上の親に置く
  • 子コンポーネントは 表示とイベント通知だけ
  • % 演算子は 負の数に注意
  • 「分けたい」と思った時が、設計を学ぶチャンス

Discussion