ChatGPTの問題集でReactの演習をしてみた（初級編）

# はじめに
こんにちは。
Reactを学びたての初学者の者です。

Reactの超基本を学習後、どうやってアウトプットしていこうかと考えていたところ、以下のような先駆者様方の記事を見つけました。

https://qiita.com/tak001/items/7605f0be7b424118e5a5

https://qiita.com/tukaji/items/69ca421912b62f647741

https://qiita.com/tomo0227/items/05b540768c67c2fc05c3

とても参考になりました。
生成AIで問題集を作成できるとは、すごい時代ですね。

## やってみた
今回は次のようなプロンプトを投げてみました。

**Reactプログラミング初級者です。
Reactの中級者になるための問題集を1問ずつ作成してください。**

**流れは下記に準じてください。
Step1:
問題は以下の条件で1問ずつ出題する。
・Reactはversion19を使用する
・問題名を表示する
・問題ごとに小さなアプリを作成できるようにする
・問題ごとに正解となる要件を明確に示す（実装する機能など）
・その問題で身に着けるべきこと、学習できることを示す
・問題ごとにヒントも示す
Step2:
私が「ヒントを追加して」「答えを教えて」と指示を出すと、ヒントや解答を示す。
Step3:
私が「解答終了」と指示を出すと、次の問題を出題する。**

## 補足
以後、この記事の流れは以下のようになります。

1. ChatGPTが出題した内容
1. 私が作成した解答 

以後問題14まで繰り返し

1のヒントと2に関しては折りたたんでいます。
私と同じようなアウトプットのやり方に悩んでいる初学者の方の参考になればと思います。

:::note warn
注意
学習中の身のため、解答に冗長な部分があると思いますがご了承ください。
動くことは確認済みです。

また、解答のindex.htmlとエントリーポイントは省略しております。
:::

# 問題 1：カウンターアプリを作成しよう
## 📌 問題名
カウンターアプリ
## 📌 正解となる要件（実装する機能）
* `+`ボタンをクリックするとカウントが1増える
* `-`ボタンをクリックするとカウントが1減る
* カウントの値は画面に表示される
* カウントの初期値は0
* カウントの値はコンポーネントの状態（state）で管理する

## 🎯 学習できること
* React 19の`useState`の使い方
* イベントハンドラーの設定方法（onClickなど）
* JSXの基本的な構文
* 関数コンポーネントの構造

<details><summary>💡 ヒント</summary>

* React 19では従来通りの`useState`が使用できます
* 状態（state）を管理するために、`useState`を`import`してください
* イベントハンドラーに関数を設定することで、クリック時の動作を指定できます
* 状態の更新は関数の引数として新しい値を渡す方法でも可能です
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  const handlePlus = () => {
    setCount(count + 1);
  };
  const handleMinus = () => {
    setCount(count - 1);
  };

  return (
    <>
      <div>count:{count}</div>
      <button onClick={handlePlus}>+</button>
      <button onClick={handleMinus}>-</button>
    </>
  );
}
```
</details>

# 問題 2：テキスト入力フィールドと表示を連動させよう
## 📌 問題名
リアルタイム文字表示アプリ
## 📌 正解となる要件（実装する機能）
* ユーザーが入力できるテキストフィールドを1つ作成する
* 入力中のテキストが、画面上にリアルタイムで表示される
* 入力値は状態（state）で管理される

## 🎯 学習できること
* `input`要素の使用方法
* フォーム要素の状態管理（controlled components）
* `onChange`イベントの扱い方
* 状態と表示のリアクティブな連携

<details><summary>💡 ヒント</summary>

* テキストの入力内容は`event.target.value`で取得できます
* `useState`で入力内容を管理しましょう
* `input`の`value`属性に状態変数を指定すると、状態と入力が連動します
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useState } from 'react'

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

  const handleChange = (e) => {
    setText(e.target.value);
  };

  return (
    <input type="text" placeholder='テキストを入力' value={text} onChange={handleChange} />
  )
}
```
</details>

# 問題 3：条件によって表示を切り替えよう
## 📌 問題名
ログイン状態によるメッセージ表示アプリ
## 📌 正解となる要件（実装する機能）
* 「ログイン」「ログアウト」ボタンを作成する
* 初期状態は「ログアウト」状態
* 状態（state）によって画面に表示されるメッセージを切り替える
  * ログイン状態のとき：「ようこそ、ユーザーさん！」
  * ログアウト状態のとき：「ログインしてください」
* ボタンを押すと、状態がトグル（交互に切り替わる）される

## 🎯 学習できること
* 状態によってUIを動的に変える方法（条件レンダリング）
* 状態のトグル（真偽値の切り替え）
* JSX内での`if`または三項演算子による条件文期の使い方

<details><summary>💡 ヒント</summary>

* ログイン状態は`boolean`（true/false）で管理できます
* 状態の切り替えには`setState(prev => !prev)`のような形が便利です
* JSX内では`{isLoggedIn ? "..." : "..."}`のように条件分岐が書けます
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useState } from "react";

export default function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleClick = () => {
    setIsLoggedIn((prev) => !prev);
  };

  return (
    <>
      <div>
        {isLoggedIn ? "ようこそユーザーさん！" : "ログインしてください"}
      </div>
      {isLoggedIn ? (
        <button onClick={handleClick}>ログアウト</button>
      ) : (
        <button onClick={handleClick}>ログイン</button>
      )}
    </>
  );
}
```
</details>

# 問題 4：配列データをリスト表示しよう

## 📌 問題名

**Todoリストの一覧表示アプリ（表示のみ）**

## 📌 正解となる要件（実装する機能）

* 以下のような初期のTodoデータを配列で持つ：

  ```js
  const todos = [
    { id: 1, title: "買い物に行く" },
    { id: 2, title: "宿題をやる" },
    { id: 3, title: "運動する" }
  ];
  ```
* この配列を画面上にリストとして表示する
* 各Todoアイテムのタイトルだけが表示される
* `ul`と`li`タグを使って、整ったリストにする

## 🎯 学習できること

* 配列データを`map()`で繰り返し描画する方法
* JSXでの`key`属性の重要性と使い方
* Reactでのリスト表示の基本

<details><summary>💡 ヒント</summary>

* `todos.map(todo => <li key={todo.id}>{todo.title}</li>)` のように書けます
* 各要素に`key`属性を設定することで、Reactがリストの再描画を正しく処理できます
* 配列はコンポーネントの外でも中でも定義できます
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
export default function App() {
  const todos = [
    { id: 1, title: "買い物に行く" },
    { id: 2, title: "宿題をやる" },
    { id: 3, title: "運動する" },
  ];

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}
```
</details>

# 問題 5：入力フォームからTodoを追加しよう

## 📌 問題名

**Todo追加アプリ（入力＋リスト表示）**

## 📌 正解となる要件（実装する機能）

* ユーザーがTodoを入力できる`input`フィールドを1つ作成する
* 「追加」ボタンをクリックすると、新しいTodoがリストに追加される
* 追加後、`input`の中身は空になる
* Todoは`ul`リストで表示される
* 各Todoはオブジェクトとして`id`と`title`を持つ形で状態に保存される
## 🎯 学習できること

* フォームの入力状態管理（Controlled Component）
* 状態に配列を使用し、要素を追加する方法
* フォーム送信イベントの処理
* ユーザー操作による状態の更新

<details><summary>💡 ヒント</summary>

* 新しいTodoはスプレッド構文（`[...]`）で既存のリストに追加できます
* `id`は`Date.now()`などで簡易的に生成できます
* `input`の値は状態で管理し、`onChange`で更新しましょう
</details>

<details><summary>作成した解答</summary>

```jsx
import React, { useState } from "react";

export default function App() {
  const [text, setText] = useState("");
  const [todos, setTodos] = useState([]);

  const handleChange = (e) => {
    setText(e.target.value);
  };

  const onClickAdd = () => {
    const date = Date.now();
    const newTodos = [
      ...todos,
      {
        id: date,
        title: text,
      },
    ];
    setTodos(newTodos);
    setText("");
  };

  return (
    <>
      <input
        type="text"
        placeholder="todoを入力"
        value={text}
        onChange={handleChange}
      />
      <button onClick={onClickAdd}>追加</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </>
  );
}
```
</details>

# 問題 6：Todoを削除できるようにしよう

## 📌 問題名

**Todo削除アプリ**

## 📌 正解となる要件（実装する機能）

* 各Todo項目の横に「削除」ボタンを表示する
* 「削除」ボタンを押すと、そのTodoがリストから削除される
* 削除後のリストが即時に画面に反映される
* Todoリストは`id`を使って一意に管理する


## 🎯 学習できること

* イベントにパラメータを渡す方法（例：どのTodoを削除するか）
* 配列の`filter()`を使って特定の要素を取り除く方法
* JSX内での繰り返しレンダリングとイベント設定の組み合わせ


<details><summary>💡 ヒント</summary>

* 削除処理では、`filter(todo => todo.id !== 削除対象のid)`が使えます
* `onClick={() => handleDelete(todo.id)}`のように書くことで、特定のidを削除できます
* 状態を更新すれば画面も自動で更新されます（Reactの再レンダリング）
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useState } from "react";

export default function App() {
  const [text, setText] = useState("");
  const [todos, setTodos] = useState([]);

  const handleChange = (e) => {
    setText(e.target.value);
  };

  const onClickAdd = () => {
    const date = Date.now();
    const newTodos = [
      ...todos,
      {
        id: date,
        title: text,
      },
    ];
    setTodos(newTodos);
    setText("");
  };

  const onClickDelete = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id));
  };

  return (
    <>
      <input
        type="text"
        placeholder="todoを入力"
        value={text}
        onChange={handleChange}
      />
      <button onClick={onClickAdd}>追加</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.title}
            <button onClick={() => onClickDelete(todo.id)}>削除</button>
          </li>
        ))}
      </ul>
    </>
  );
}
```
</details>

# 問題 7：useEffectを使ってみよう

## 📌 問題名

**ページ読み込み時にデータを取得するアプリ（モック）**

## 📌 正解となる要件（実装する機能）

* コンポーネントの初回マウント時（ページ読み込み時）に、`useEffect`を使ってデータを取得する
* データは以下のような配列（モック）を1秒後に取得したように見せる（setTimeoutを使用）：

  ```js
  const mockData = [
    { id: 1, title: "サンプル1" },
    { id: 2, title: "サンプル2" }
  ];
  ```
* データ取得中は「読み込み中...」と表示する
* データ取得後、リスト表示する

## 🎯 学習できること

* `useEffect`の基本的な使い方
* 非同期処理のシミュレーション（`setTimeout`）
* 状態の初期化と更新
* 条件付きレンダリング（ローディング表示）

<details><summary>💡 ヒント</summary>

* `useEffect(() => { ... }, [])`は初回マウント時に一度だけ実行されます
* データ取得中は、`isLoading`という`boolean`の状態を使って制御できます
* `setTimeout`を使うとAPI風の非同期処理を模擬できます
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useEffect, useState } from "react";

const mockData = [
  { id: 1, title: "サンプル1" },
  { id: 2, title: "サンプル2" },
];

export default function App() {
  const [isLoading, setIsLoading] = useState(null);
  const [data, setData] = useState([]);

  useEffect(() => {
    setTimeout(() => {
      setData(mockData);
      setIsLoading(true);
    }, 1000);
  }, []);

  return (
    <>
      {isLoading ? (
        <ul>
          {data.map((datum) => (
            <li key={datum.id}>{datum.title}</li>
          ))}
        </ul>
      ) : (
        <p>読み込み中...</p>
      )}
    </>
  );
}
```
</details>

# 問題 8：複数のstateを管理しよう

## 📌 問題名

**カウント＆メモ表示アプリ**

## 📌 正解となる要件（実装する機能）

* 「カウント」と「メモ」の2つの情報を入力・表示できるようにする
* 以下の2つの状態をそれぞれ管理する：

  * カウント（`+` ボタンで1ずつ増加）
  * メモ（テキスト入力フィールドで入力）
* それぞれの値は画面に表示される
* カウントとメモは別々の`useState`で管理する

## 🎯 学習できること

* 複数の状態（state）の独立管理
* 状態を組み合わせたUIの構成
* 状態が多くなっても明確に構造化する工夫

<details><summary>💡 ヒント</summary>

* `useState`は1つのコンポーネント内で複数回使うことができます
* `const [count, setCount] = useState(0);`
  `const [memo, setMemo] = useState("");` のように使いましょう
* JSXの中でそれぞれの状態を適切に表示させることが大切です
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from "react";
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  const handleClick = () => {
    setCount(count + 1);
  };

  const handleChange = (e) => {
    setText(e.target.value);
  } ;

  return (
    <>
      <div>
        カウント：{count}
        <button onClick={handleClick}>+</button>
      </div>
      <div>
        メモ：
        <input type="text" placeholder="メモを入力" value={text} onChange={handleChange}/>
      </div>
    </>
  );
}
```
</details>

# 問題 9：子コンポーネントにpropsを渡そう

## 📌 問題名

**プロフィール表示コンポーネントの作成**

## 📌 正解となる要件（実装する機能）

* 親コンポーネントから`name`と`age`の情報を子コンポーネントに渡す
* 子コンポーネントは受け取った`name`と`age`を画面に表示する
* 表示内容の例：`名前：田中太郎 / 年齢：25歳`
* propsの受け取り方を正しく理解して使う

## 🎯 学習できること

* `props`の基本的な使い方
* 親から子へのデータの受け渡し
* コンポーネントの分割と再利用性の向上

<details><summary>💡 ヒント</summary>

* 子コンポーネントは、引数として`props`または`{ name, age }`のように受け取れます
* 親で`<Profile name="田中太郎" age={25} />`のように渡しましょう
* 分割代入を使うと見やすくなります：`function Profile({ name, age }) { ... }`
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from 'react'
import Children from './Children'

export default function App() {
  return (
    <Children name={"田中太郎"} age={25} />
  )
}
```
```jsx:Children.jsx
import React from 'react'

export default function Children({ name, age }) {
  return (
    <>
      <div>名前：{name}</div>
      <div>年齢：{age}歳</div>
    </>
  )
}
```
</details>

# 問題 10：親コンポーネントから子コンポーネントへイベント関数を渡そう

## 📌 問題名

**子コンポーネントからカウントを増やすボタンを押すアプリ**

## 📌 正解となる要件（実装する機能）

* 親コンポーネントで`count`を状態として管理する
* 子コンポーネントに「カウントを増やす」ボタンを配置する
* 子コンポーネントでボタンがクリックされると、親の`count`が1増える
* カウントの値は親コンポーネントで表示する

## 🎯 学習できること

* コールバック関数をpropsとして渡す方法
* 子コンポーネントから親の関数を呼び出す仕組み
* 状態リフトアップ（状態を親に保持し、子で操作）

<details><summary>💡 ヒント</summary>

* 親で`handleIncrement`関数を定義し、それを子に`onIncrement`として渡します
* 子では`onClick={onIncrement}`のように使います
* 状態は必ず親で管理し、子では変更の「きっかけ」だけを作るようにします
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React, { useState } from "react";
import Children from "./Children";

export default function App() {
  const [count, setCount] = useState(0);

  const handleIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <div>カウント：{count}</div>
      <Children onIncrement={handleIncrement} />
    </>
  );
}
```
```jsx:Children.jsx
import React from 'react'

export default function Children({ onIncrement }) {
  return (
    <button onClick={onIncrement}>カウントを増やす</button>
  )
}
```
</details>

# 問題 11：チェックボックスで状態を切り替えよう

## 📌 問題名

**Todoの完了状態をチェックできるアプリ**

## 📌 正解となる要件（実装する機能）

* 複数のTodo項目（id, title, completed）を状態として保持する
* 各Todoの横にチェックボックスを表示する
* チェックボックスをクリックすると、そのTodoの`completed`状態（true/false）がトグルされる
* `completed: true`のTodoは文字が取り消し線（`text-decoration: line-through`）で表示される

## 🎯 学習できること

* チェックボックスの扱い方（`checked`と`onChange`）
* 状態の配列を更新する方法（特定の項目だけ書き換え）
* 条件に応じてCSSスタイルを動的に変更する方法

<details><summary>💡 ヒント</summary>

* チェック状態は`checked={todo.completed}`で制御できます
* `onChange`で該当のTodoの`completed`値を反転させましょう
* 条件付きでスタイルを適用するには、インラインで`style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}`のように書けます
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from "react";
import { useState } from "react";

export default function App() {
  const [todos, setTodos] = useState([]);
  const [text, setText] = useState("");

  const handleChangeText = (e) => {
    setText(e.target.value);
  };

  const onClickAdd = () => {
    const date = Date.now();
    setTodos([...todos, {
      id: date,
      title: text,
      isCompleted: false,
    }]);
    setText("");
  };

  const handleChangeCheckbox = (id) => {
    const updateTodos = todos.map((todo) =>
      todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
    );
    setTodos(updateTodos);
  };

  return (
    <>
      <input
        type="text"
        placeholder="todoを入力"
        value={text}
        onChange={handleChangeText}
      />
      <button onClick={onClickAdd}>追加</button>

      <div>
        <p>todo一覧</p>
        <ul>
          {todos.map((todo) => (
            <li key={todo.id}>
              <span
                style={{
                  textDecoration: todo.isCompleted ? "line-through" : "none",
                }}
              >
                {todo.title}
              </span>
              <input
                type="checkbox"
                onChange={() => handleChangeCheckbox(todo.id)}
                checked={todo.isCompleted}
              />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}
```
</details>

# 問題 12：フォームの送信を防いで処理しよう

## 📌 問題名

**送信イベント処理アプリ**

## 📌 正解となる要件（実装する機能）

* フォーム内に`input`と`button`を1つずつ作成する
* ボタンをクリック（またはEnterキーで送信）すると、フォームの送信イベントが発生する
* 送信イベントを防止し、ページのリロードをさせない
* 入力内容を取得し、画面に表示する

## 🎯 学習できること

* フォーム送信イベント（`onSubmit`）の処理方法
* `event.preventDefault()`の重要性
* `form`タグと`input`/`button`の連携

<details><summary>💡 ヒント</summary>

* `form`タグに`onSubmit={handleSubmit}`を設定しましょう
* `handleSubmit`関数内で`event.preventDefault()`を呼ぶことで、ページの再読み込みを防げます
* 入力値は`useState`で管理し、送信時にその内容を表示すればOKです
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from 'react'
import { useState } from 'react'

export default function App() {
  const [text, setText] = useState("");
  const [show, setShow] = useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    setShow(text);
    setText("");
  };

  return (
    <>
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder='テキストを入力' value={text} onChange={handleChange}/>
      <button>送信</button>
    </form>
    <div>
      最新の入力：{show}
    </div>
    </>
  )
}
```
</details>

# 問題 13：ラジオボタンで選択肢を切り替えよう

## 📌 問題名

**好きな色を選ぶフォームアプリ**

## 📌 正解となる要件（実装する機能）

* 「赤」「青」「緑」の3つのラジオボタンを用意する
* ユーザーが1つを選ぶと、その色名が画面に表示される
* ラジオボタンの選択状態は`useState`で管理する
* ラジオボタンの値と`checked`状態を正しく連動させる

## 🎯 学習できること

* `input type="radio"`の使い方
* ラジオボタンのグルーピングと`name`属性の重要性
* 選択肢の状態を1つのstateで管理する方法

<details><summary>💡 ヒント</summary>

* 各`input`に`value`を設定し、状態と`checked`を連動させましょう
* 例：

  ```jsx
  <input
    type="radio"
    name="color"
    value="赤"
    checked={selectedColor === "赤"}
    onChange={(e) => setSelectedColor(e.target.value)}
  />
  ```
* 複数の選択肢があっても、状態は1つで十分です
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from "react";
import { useState } from "react";

export default function App() {
  const [selectedColor, setSelectedColor] = useState(null);

  return (
    <>
      <h1>選んだ色：{selectedColor}</h1>
      <input
        type="radio"
        id="red"
        name="color"
        value={"赤"}
        checked={selectedColor === "赤"}
        onChange={(e) => setSelectedColor(e.target.value)}
      />
      <label htmlFor="red">赤</label>
      <input
        type="radio"
        id="blue"
        name="color"
        value={"青"}
        checked={selectedColor === "青"}
        onChange={(e) => setSelectedColor(e.target.value)}
      />
      <label htmlFor="blue">青</label>
      <input
        type="radio"
        id="green"
        name="color"
        value={"緑"}
        checked={selectedColor === "緑"}
        onChange={(e) => setSelectedColor(e.target.value)}
      />
      <label htmlFor="green">緑</label>
    </>
  );
}
```
</details>

# 問題 14：selectタグで選択肢を切り替えよう

## 📌 問題名

**国を選ぶセレクトボックスアプリ**

## 📌 正解となる要件（実装する機能）

* セレクトボックス（`<select>`）で「日本」「アメリカ」「フランス」から1つを選べるようにする
* 選択された国の名前を画面に表示する
* 選択状態は`useState`で管理する
* `onChange`イベントを使って選択肢の変更に対応する

## 🎯 学習できること

* `<select>`と`<option>`の使い方
* フォーム要素の状態管理（controlled component）
* 選択肢の変更に応じて画面表示を更新する方法

<details><summary>💡 ヒント</summary>

* `select`タグには`value`属性を設定し、状態と連動させます
* `onChange={e => setSelectedCountry(e.target.value)}` のようにして状態を更新します
* `option`にはそれぞれ異なる`value`を設定しておきましょう
</details>

<details><summary>作成した解答</summary>

```jsx:App.jsx
import React from 'react'
import { useState } from 'react'

export default function App() {
  const [selectedCountry, setSelectedCountry] = useState("");

  const handleChange = (e) => {
    setSelectedCountry(e.target.value);
  };

  return (
    <>
      <h1>選んだ国名：{selectedCountry}</h1>
      <select value={selectedCountry} onChange={handleChange}>
        <option value="">国を選んでください</option>
        <option value="日本">日本</option>
        <option value="アメリカ">アメリカ</option>
        <option value="フランス">フランス</option>
      </select>
    </>
  )
}
```
</details>

# 終わりに
今回14問で終わっているのは訳がありまして、15問目を出力したところ、既に解答済みの問題とほぼ同様の問題でした。

そこで

**まだ出題していない内容の問題をお願いします。**

**もし初級者のレベルを卒業したと判断できたら、中級者向けの問題を出題してください。**

と投げてみたところ

>ご指摘ありがとうございます。そして、おめでとうございます！🎉
あなたはReact初級者レベルをしっかりと卒業できています。これからは中級者向けの問題にステップアップしましょう！

と返ってきました。
ChatGPT的には問題14まで解けたら初級者は卒業みたいです。