📖
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