Reactとバックエンドで作る！実践Web開発入門 | 第5章: Reactの状態管理を強化

## はじめに
第4章でReactとバックエンドを連携しました。今回は、アプリが複雑になるにつれて必要となる「状態管理」を強化します。具体的には、Context APIを使って商品リストをグローバルに管理する方法を学びます。

## 目標
- Context APIの基本を理解する  
- グローバルな状態を管理する  
- 商品追加機能を追加する  

## Context APIとは
Context APIは、Reactでコンポーネント間でデータを簡単に共有するための仕組みです。Propsを何層も渡す必要がなくなり、コードがシンプルになります。

## Contextの作成
`frontend/src`に`ProductContext.tsx`を作成し、以下を追加：
```tsx
import React, { createContext, useState, useContext } from 'react';

const ProductContext = createContext();

export const ProductProvider = ({ children }) => {
  const [products, setProducts] = useState([]);

  const addProduct = (newProduct) => {
    setProducts((prev) => [...prev, { ...newProduct, id: Date.now() }]);
  };

  return (
    <ProductContext.Provider value={{ products, setProducts, addProduct }}>
      {children}
    </ProductContext.Provider>
  );
};

export const useProductContext = () => useContext(ProductContext);
```

## Appコンポーネントの更新
`App.tsx`を以下のように修正し、Contextを利用：
```tsx
import React, { useEffect } from 'react';
import './App.css';
import { ProductProvider, useProductContext } from './ProductContext';

function App() {
  return (
    <ProductProvider>
      <div className="App">
        <Header />
        <ProductList />
        <Footer />
      </div>
    </ProductProvider>
  );
}

const Header = () => (
  <header>
    <h1>商品リスト</h1>
  </header>
);

const ProductList = () => {
  const { products, setProducts, addProduct } = useProductContext();
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [newProduct, setNewProduct] = React.useState({ name: '', price: '' });

  useEffect(() => {
    const fetchProducts = async () => {
      try {
        const response = await fetch('http://localhost:5000/products');
        if (!response.ok) throw new Error('データの取得に失敗しました');
        const data = await response.json();
        setProducts(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchProducts();
  }, [setProducts]);

  const handleAddProduct = (e) => {
    e.preventDefault();
    if (newProduct.name && newProduct.price) {
      addProduct({ name: newProduct.name, price: Number(newProduct.price) });
      setNewProduct({ name: '', price: '' });
    }
  };

  if (loading) return <main>読み込み中...</main>;
  if (error) return <main>エラー: {error}</main>;

  return (
    <main>
      <h2>商品一覧</h2>
      <form onSubmit={handleAddProduct}>
        <input
          type="text"
          placeholder="商品名"
          value={newProduct.name}
          onChange={(e) => setNewProduct({ ...newProduct, name: e.target.value })}
        />
        <input
          type="number"
          placeholder="価格"
          value={newProduct.price}
          onChange={(e) => setNewProduct({ ...newProduct, price: e.target.value })}
        />
        <button type="submit">追加</button>
      </form>
      <ul>
        {products.map((product) => (
          <ProductItem key={product.id} name={product.name} price={product.price} />
        ))}
      </ul>
    </main>
  );
};

const ProductItem = ({ name, price }) => (
  <li>
    {name} - {price}円
  </li>
);

const Footer = () => (
  <footer>
    <p>© 2025 React Web開発入門</p>
  </footer>
);

export default App;
```

## CSSの調整
`App.css`にフォーム用のスタイルを追加：
```css
form {
  margin: 20px 0;
}

input {
  padding: 5px;
  margin-right: 10px;
}

button {
  padding: 5px 10px;
  background-color: #61dafb;
  border: none;
  cursor: pointer;
}

button:hover {
  background-color: #21a1f1;
}
```

## 動作確認
1. バックエンドを起動（`node index.js`）。
2. フロントエンドを起動（`npm start`）。
3. `http://localhost:3000`で確認。商品リストが表示され、フォームで新しい商品を追加できます。
- 注意: 今回は追加した商品はバックエンドに保存されません（次章で対応）。

## 次回予告
第6章では、バックエンドにデータベースを追加し、商品データを永続的に保存する方法を学びます。お楽しみに！

---

この記事が役に立ったら、ぜひ「いいね」や「ストック」をお願いします！質問や感想はコメント欄で気軽にどうぞ。次回も一緒に学びましょう！