React 実践演習問題

2025-08-09

はじめに

この演習では、Reactの基礎を学んだばかりの方向けに2つのアプリケーション開発を通じて実践的なスキルを身につけます。最初にState管理を学ぶToDoアプリ、次にAPI連携を学ぶ天気アプリを作成します。

演習1: ToDoアプリ(State管理)

目標

  • ReactのuseStateフックを使った状態管理を理解する
  • フォーム入力とリスト表示の実装方法を学ぶ
  • コンポーネント間でのデータの受け渡しを理解する

手順

1. プロジェクトのセットアップ

npx create-react-app todo-app
cd todo-app
npm start

2. 基本的なコンポーネント構造を作成

src/App.jsを以下のように編集します:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="app">
      <h1>ToDoアプリ</h1>
      {/* ここにToDoフォームとリストを追加 */}
    </div>
  );
}

export default App;

3. ToDoの状態管理を実装

Appコンポーネント内でuseStateを使ってToDoリストを管理します。

import React, { useState } from 'react';

function App() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  // ここに関数を追加していきます

  return (
    // ...
  );
}

4. ToDo追加機能の実装

以下の関数をAppコンポーネント内に追加してください:

const addTodo = () => {
  if (inputValue.trim()) {
    setTodos([...todos, { text: inputValue, completed: false }]);
    setInputValue('');
  }
};

5. フォームの作成

return文内に以下のJSXを追加:

<div className="todo-form">
  <input
    type="text"
    value={inputValue}
    onChange={(e) => setInputValue(e.target.value)}
    placeholder="新しいToDoを入力"
  />
  <button onClick={addTodo}>追加</button>
</div>

6. ToDoリストの表示

以下のJSXを追加:

<ul className="todo-list">
  {todos.map((todo, index) => (
    <li key={index}>
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
    </li>
  ))}
</ul>

7. 完成形のコード

最終的なApp.jsは以下のようになります:

import React, { useState } from 'react';
import './App.css';

function App() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    if (inputValue.trim()) {
      setTodos([...todos, { text: inputValue, completed: false }]);
      setInputValue('');
    }
  };

  const toggleTodo = (index) => {
    const newTodos = [...todos];
    newTodos[index].completed = !newTodos[index].completed;
    setTodos(newTodos);
  };

  return (
    <div className="app">
      <h1>ToDoアプリ</h1>
      <div className="todo-form">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="新しいToDoを入力"
        />
        <button onClick={addTodo}>追加</button>
      </div>
      <ul className="todo-list">
        {todos.map((todo, index) => (
          <li key={index} onClick={() => toggleTodo(index)}>
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

8. スタイルの追加

src/App.cssに以下のスタイルを追加:

.app {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.todo-form {
  display: flex;
  margin-bottom: 20px;
}

.todo-form input {
  flex-grow: 1;
  padding: 8px;
  font-size: 16px;
}

.todo-form button {
  padding: 8px 16px;
  margin-left: 10px;
  background-color: #4CAF50;
  color: white;
  border: none;
  cursor: pointer;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-list li {
  padding: 10px;
  margin-bottom: 5px;
  background-color: #f5f5f5;
  cursor: pointer;
}

.todo-list li:hover {
  background-color: #e9e9e9;
}

発展課題(余裕がある人向け)

  1. ToDoの削除機能を追加する
  2. 完了済みToDoをフィルタリングする機能を追加する
  3. ToDoの編集機能を追加する
  4. localStorageを使ってToDoを永続化する

演習2: 天気アプリ(公開API使用)

目標

  • 外部APIとの連携方法を学ぶ
  • useEffectフックの使い方を理解する
  • 非同期処理の扱い方を学ぶ
  • 条件付きレンダリングを実践する

手順

1. プロジェクトのセットアップ

npx create-react-app weather-app
cd weather-app
npm start

2. 必要なライブラリのインストール

npm install axios

3. 基本コンポーネントの作成

src/App.jsを以下のように編集:

import React, { useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
  const [city, setCity] = useState('');
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  // ここに関数を追加していきます

  return (
    <div className="app">
      <h1>天気予報アプリ</h1>
      {/* ここに検索フォームと天気表示を追加 */}
    </div>
  );
}

export default App;

4. API呼び出し関数の実装

OpenWeatherMap APIを使用します(無料で利用可能)。まずはAPIキーを取得してください:

  1. OpenWeatherMapにアクセス
  2. アカウントを作成(無料)
  3. API Keysページでキーを取得

以下の関数をAppコンポーネント内に追加:

const fetchWeather = async () => {
  if (!city) return;

  setLoading(true);
  setError('');

  try {
    const response = await axios.get(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric&lang=ja`
    );
    setWeather(response.data);
  } catch (err) {
    setError('都市が見つかりませんでした');
    setWeather(null);
  } finally {
    setLoading(false);
  }
};

5. フォームの作成

return文内に以下のJSXを追加:

<div className="search-form">
  <input
    type="text"
    value={city}
    onChange={(e) => setCity(e.target.value)}
    placeholder="都市名を入力(例: Tokyo)"
  />
  <button onClick={fetchWeather} disabled={loading}>
    {loading ? '取得中...' : '天気を取得'}
  </button>
</div>

6. 天気情報の表示

以下のJSXを追加:

{error && <div className="error">{error}</div>}

{weather && (
  <div className="weather-card">
    <h2>{weather.name}の天気</h2>
    <div className="weather-info">
      <img
        src={`http://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
        alt={weather.weather[0].description}
      />
      <p>{weather.weather[0].description}</p>
      <p>気温: {weather.main.temp}°C</p>
      <p>湿度: {weather.main.humidity}%</p>
      <p>風速: {weather.wind.speed}m/s</p>
    </div>
  </div>
)}

7. 完成形のコード

最終的なApp.jsは以下のようになります:

import React, { useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
  const [city, setCity] = useState('');
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const fetchWeather = async () => {
    if (!city) return;

    setLoading(true);
    setError('');

    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric&lang=ja`
      );
      setWeather(response.data);
    } catch (err) {
      setError('都市が見つかりませんでした');
      setWeather(null);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="app">
      <h1>天気予報アプリ</h1>
      <div className="search-form">
        <input
          type="text"
          value={city}
          onChange={(e) => setCity(e.target.value)}
          placeholder="都市名を入力(例: Tokyo)"
        />
        <button onClick={fetchWeather} disabled={loading}>
          {loading ? '取得中...' : '天気を取得'}
        </button>
      </div>

      {error && <div className="error">{error}</div>}

      {weather && (
        <div className="weather-card">
          <h2>{weather.name}の天気</h2>
          <div className="weather-info">
            <img
              src={`http://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
              alt={weather.weather[0].description}
            />
            <p>{weather.weather[0].description}</p>
            <p>気温: {weather.main.temp}°C</p>
            <p>湿度: {weather.main.humidity}%</p>
            <p>風速: {weather.wind.speed}m/s</p>
          </div>
        </div>
      )}
    </div>
  );
}

export default App;

8. スタイルの追加

src/App.cssに以下のスタイルを追加:

.app {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

.search-form {
  display: flex;
  margin-bottom: 20px;
}

.search-form input {
  flex-grow: 1;
  padding: 10px;
  font-size: 16px;
}

.search-form button {
  padding: 10px 20px;
  margin-left: 10px;
  background-color: #2196F3;
  color: white;
  border: none;
  cursor: pointer;
}

.search-form button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.error {
  color: #f44336;
  margin-bottom: 20px;
}

.weather-card {
  background-color: #f5f5f5;
  padding: 20px;
  border-radius: 8px;
}

.weather-info img {
  width: 100px;
  height: 100px;
}

.weather-info p {
  margin: 10px 0;
  font-size: 18px;
}

発展課題(余裕がある人向け)

  1. 現在位置を使用して天気を自動表示する機能を追加
  2. 5日間の天気予報を表示する機能を追加
  3. 天気に応じて背景色を変更する
  4. 都市名の入力候補を表示するオートコンプリート機能を追加
  5. 温度の単位を切り替える機能を追加(℃/℉)

まとめ

この演習を通じて、Reactの基本的な概念である状態管理(useState)、副作用処理(useEffect)、コンポーネントのライフサイクル、外部APIとの連携方法などを実践的に学ぶことができました。ToDoアプリではReactの基本的なデータフローを、天気アプリでは非同期処理と外部リソースの活用方法を理解することが目的でした。

さらに学びを深めたい場合は、Reactの公式ドキュメントや、より複雑なプロジェクトに挑戦してみてください。