React · LEARN AT YOUR OWN PACE今日も、一歩ずつ。

LESSON 12 · 約45分

小さな学習リストを完成し、Next.jsへ進む

学んだ要素を一つの画面へつなぎ、Reactとフレームワークの境界を整理します。

コードを試す前に

01のVite reactテンプレート(JavaScript/JSX)を使います。特記のないApp.jsxは、そのレッスンだけで動く独立した例です。例同士を連結しないでください。

短い学習用の例です。各App.jsxは独立した例で、順に追記して一つの完成品にする形式ではありません。ファイル名、依存関係、未定義部分の前提は注記に記載します。問題内の抜粋や空欄は、そのまま実行するための完成コードではありません。

開発環境の準備を確認する →

このレッスンでわかること

  • 追加・切り替え・絞り込みを持つ小さなアプリを完成できる
  • Reactで学んだこととNext.jsで新たに学ぶことを区別できる

先に完成条件を決める

学習テーマの追加、完了の切り替え、未完了だけの表示、残件数、0件表示を作ります。最初は端末内のstateだけで動かし、再読み込みで戻ることを明示します。ContextやEffectは、学んだからという理由だけでは追加しません。必要な状態は項目・入力・表示条件です。

完成例から、自分の設計へ

下の例でデータの流れを確かめたら、フォームと一覧を別部品へ分けます。項目を管理するstateは共通の親に残し、子には値と通知関数を渡します。idは追加時に一度決め、描画のたびに作り直しません。提出時は、動いた画面だけでなく空欄・全完了・0件・再読み込みも説明します。

Next.jsはReactの先につながる

Next.jsでもコンポーネント、props、stateの知識を使います。App Routerではページやレイアウトは標準でServer Componentです。stateやイベントを使う部品はClient境界へ置きます。Client Componentも初回HTMLの準備にサーバーで関わる場合があり、「use client=すべてブラウザーでしか動かない」とは限りません。次はルート、Server/Client境界、データ取得、保存を順に学びましょう。

  • Viteのこの演習ではuse clientは不要
  • Next.jsでは対話部品の入口にuse clientを付ける。すべてのファイルに付ける必要はない
  • クライアントに置くコードへ秘密のAPIキーを入れない。永続保存や権限は別に設計する

完成例: 今日の学習リスト

src/App.jsx
import { useRef, useState } from 'react';

export default function App() {
  const [items, setItems] = useState([
    { id: 1, title: 'Reactの部品を説明する', done: false },
    { id: 2, title: '四択問題で復習する', done: false },
  ]);
  const [title, setTitle] = useState('');
  const [onlyTodo, setOnlyTodo] = useState(false);
  const [error, setError] = useState('');
  const nextId = useRef(3);
  const remaining = items.filter(item => !item.done).length;
  const visible = onlyTodo ? items.filter(item => !item.done) : items;

  function addItem(event) {
    event.preventDefault();
    const value = title.trim();
    if (!value) { setError('テーマを入力してください。'); return; }
    const newItem = { id: nextId.current++, title: value, done: false };
    setItems(current => [...current, newItem]);
    setTitle('');
    setError('');
  }

  function toggleItem(id) {
    setItems(current => current.map(item =>
      item.id === id ? { ...item, done: !item.done } : item
    ));
  }

  return (
    <main>
      <h1>今日の学習リスト</h1>
      <p>この練習では、再読み込みすると最初の状態に戻ります。</p>
      <form onSubmit={addItem} noValidate>
        <label htmlFor="new-topic">追加するテーマ</label>
        <input id="new-topic" value={title}
          onChange={event => { setTitle(event.target.value); setError(''); }}
          aria-invalid={Boolean(error)}
          aria-describedby={error ? 'add-error' : undefined} />
        <button type="submit">追加する</button>
        {error && <p id="add-error" role="alert">{error}</p>}
      </form>
      <label>
        <input type="checkbox" checked={onlyTodo}
          onChange={event => setOnlyTodo(event.target.checked)} />
        未完了だけ表示
      </label>
      <p role="status">残り{remaining}件 / 全{items.length}件</p>
      <ul>
        {visible.map(item => (
          <li key={item.id}>
            <label>
              <input type="checkbox" checked={item.done}
                onChange={() => toggleItem(item.id)} />
              {item.title}({item.done ? '完了' : '未完了'})
            </label>
          </li>
        ))}
      </ul>
      {visible.length === 0 && <p>表示するテーマはありません。</p>}
    </main>
  );
}

このApp.jsxだけで動く統合例です。ID用refの変更はイベント中に行い、state更新関数は純粋に保ちます。データ保存・認証・通信は含みません。

TRY IT YOURSELF

学習リストを仕上げる

  1. 完成例を動かし、空欄・追加・完了・未完了絞り込み・0件の順に確認する
  2. AddTopicFormとTopicListへ分割し、stateの持ち主を説明する
  3. 削除ボタンを追加し、安定したkeyと不変更新を保つ
  4. 追加→完了→絞り込みを自動テストし、キーボードでも試す
  5. 再読み込みの挙動と、永続保存を追加するなら何が必要かを説明する

できたらOK:追加・完了・絞り込みが一貫して動き、0件と空欄に対応し、保存範囲とNext.jsで学ぶ次の項目を説明できる

QUICK CHECK

理解を確かめよう

この教材のstateだけの学習リストを再読み込みすると初期状態に戻った。適切な理解はどれ?

公式資料でもう少し詳しく

ここまで読めたら、ひとつ前進。