【備忘録】日本一わかりやすいReact-Redux講座 実践編 #12前半 「トランザクションを使って商品を注文しよう（前半）」

## はじめに
### 概要
この記事は、トラハック氏([@torahack_](https://twitter.com/torahack_))が運営するYoutubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『[日本一わかりやすいReact-Redux講座 実践編](https://www.youtube.com/watch?v=8fNgXnVTULU)』の学習備忘録です。

前回の講座で、ヘッダーのカートアイコンのバッチ数を、DB情報とリアルタイム同期させることができました。

今回は、カート商品の購入処理を実装する前段階として、カート詳細ページのビューファイルを作ります。

※ 前回記事: [【【備忘録】日本一わかりやすいReact-Redux講座 実践編 #11 「Firestoreでリアルタイムに表示を更新しよう」](https://qiita.com/ddpmntcpbr/items/57ef0619b7b29e4edf08)


### 動画URL
[トランザクションを使って商品を注文しよう（前半）【日本一わかりやすいReact-Redux講座 実践編#12】](https://www.youtube.com/watch?v=62ZTwzn5_Qw&t=418s)

### 要点
- リスト表示を実装する際は、``map``メソッドでリストアイテム用コンポーネントをイテレートする。

### 完成系イメージ

![12-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/71180559-ec6c-b802-008e-c3425cac9e51.gif)

ヘッダーのカートアイコンをクリックすると、カート情報ページ( path: ``http://localhost:3000/cart``)へ遷移します。

カートに入れた商品が表示されています。ゴミ箱アイコンを押すことで、カートから商品を削除できます。

## メイン

**``http://localhost:3000/cart``**に対して、カート情報ページを作成します。``CartList.jsx``テンプレートに対して``CartListItem.jsx``を子コンポーネントとして配置します。

![12-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/246903c7-5944-6af7-c6ef-f154ea33ba57.png)

``UIKit``コンポーネントして``GreyBottun.jsx``も追加します。

### ファイル実装

```:実装ファイル
1.src/components/UIkit/GreyButton.jsx
2.src/components/UIkit/index.js
3.src/templates/CartList.jsx
4.src/templates/index.js
5.src/components/Products/CartListItem.jsx
6.src/components/Products/index.js
7.src/Router.jsx
```

```jsx:1.src/components/UIkit/GreyButton.jsx
import React from "react";
import Button from "@material-ui/core/Button";
import {makeStyles} from "@material-ui/styles";

const useStyles = makeStyles((theme) => ({
  "button": {
    backgroundColor: theme.palette.grey["300"],
    fontSize: 16,
    height: 48,
    marginButton: 16,
    width: 256
  }
}))

const GreyButton = (props) => {

  const classes = useStyles();

  return(
    <Button className={classes.button} variant="contained" onClick={() => props.onClick()}>
      {props.label}
    </Button>
  )
}

export default GreyButton
```

```js:2.src/components/UIkit/index.js
export {default as GreyButton} from "./GreyButton"
export {default as PrimaryButton} from "./PrimaryButton"
export {default as SelectBox} from "./SelectBox"
export {default as TextInput} from "./TextInput"
```

``PrimaryButton.jsx``の色を変えただけです。

```jsx:3.src/templates/CartList.jsx
import React, { useCallback } from "react";
import {useDispatch, useSelector} from  "react-redux"
import List from "@material-ui/core/List"
import {makeStyles} from "@material-ui/core/styles";
import { getProductsInCart } from "../reducks/users/selectors";
import {CartListItem} from "../components/Products"
import {PrimaryButton, GreyButton} from "../components/UIkit"
import {push} from "connected-react-router"

const useStyles = makeStyles((theme) => ({
  root: {
    margin: '0 auto',
    maxWidth: 512,
    width: '100%'
  },
}));

const CartList = () => {
  const classes = useStyles();
  const dispatch = useDispatch();
  const selector = useSelector((state)=>state);
  const productsInCart = getProductsInCart(selector);

  const goToOrder = useCallback(() => {
    dispatch(push("/order/confirm"))
  },[])

  const backToTop = useCallback(() => {
    dispatch(push("/"))
  },[])

  return (
    <section className="c-section-wrapin">
      <h2 className="u-text__headline">ショッピングカート</h2>
      <List className={classes.root}>
        {productsInCart.length > 0 && (
            productsInCart.map(product => <CartListItem product={product} key={product.cartId} />)
        )}
      </List>
      <div className="module-spacer--medium" />
      <div className="p-grid__column">
        <PrimaryButton label={"レジへ進む"} onClick={goToOrder} />
        <div className="module-spacer--extra-extra-small"/>
        <GreyButton label={"ショッピングを続ける"} onClick={backToTop} />
      </div>
    </section>
  )
}

export default CartList
```

``map``メソッドで、リストアイテムの子コンポーネントを繰り返し描画するのは、もうお馴染みですね。

```js:4.src/templates/index.js
export {default as CartList} from './CartList' //追記
export {default as Home} from './Home'
export {default as ProductDetail} from './ProductDetail'
export {default as ProductEdit} from './ProductEdit'
export {default as ProductList} from './ProductList'
export {default as Reset} from './Reset'
export {default as SignIn} from './SignIn'
export {default as SignUp} from './SignUp'
```

```jsx:5.src/components/Products/CartListItem.jsx
import React from 'react';
import Divider from '@material-ui/core/Divider';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import {makeStyles} from "@material-ui/styles";
import DeleteIcon from "@material-ui/icons/Delete";
import IconButton from "@material-ui/core/IconButton";
import {useSelector} from "react-redux";
import {getUserId} from "../../reducks/users/selectors";
import {db} from "../../firebase/index"

const useStyles = makeStyles((theme)=>({
  list: {
    height: 128,
  },
  image: {
    objectFit: "cover",
    margin: 16,
    height: 96,
    width: 96
  },
  text: {
    width: "100%"
  }
}));

const CartListItem = (props) => {
  const classes = useStyles();
  const selector = useSelector(state => state);
  const uid = getUserId(selector)
  const image = props.product.images[0].path;
  const name = props.product.name
  const price = props.product.price.toLocaleString();
  const size = props.product.size

  const removeProductFromCart = (id) => {
    return  db.collection("users").doc(uid)
              .collection("cart").doc(id)
              .delete()
  }

  return (
    <>
      <ListItem className={classes.list}>
        <ListItemAvatar>
          <img className={classes.image} src={image} alt="商品画像"/>
        </ListItemAvatar>
        <div className={classes.text}>
          <ListItemText
            primary={name}
            secondary={"サイズ：" + size}
          />
          <ListItemText
            primary={"¥" + price}
          />
        </div>
        <IconButton onClick={() => removeProductFromCart(props.product.cartId)}>
          <DeleteIcon/>
        </IconButton>
      </ListItem>
      <Divider/>
    </>
  );

};

export default CartListItem
```

``removeProductFromCart()``で、カートの追加した商品をDB上から削除できるようにしています。

```js:6.src/components/Products/index.js
export {default as CartListItem} from "./CartListItem" //追記
export {default as ImageArea} from "./ImageArea"
export {default as ImagePreview} from "./ImagePreview"
export {default as ImageSwiper} from "./ImageSwiper"
export {default as ProductCard} from "./ProductCard"
export {default as SetSizeArea} from "./SetSizeArea"
export {default as SizeTable} from "./SizeTable"
```

```js:7.src/Router.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {CartList, ProductDetail,ProductEdit,ProductList,Reset,SignIn,SignUp} from "./templates";
import Auth from "./Auth"

const Router = () => {
  return (
    <Switch>
      <Route exact path={"/signup"} component={SignUp} />
      <Route exact path={"/signin"} component={SignIn} />
      <Route exact path={"/signin/reset"} component={Reset} />

      <Auth>
        <Route exact path={"(/)?"} component={ProductList} />
        <Route exact path={"/product/:id"} component={ProductDetail} />
        <Route path={"/product/edit(/:id)?"} component={ProductEdit} />
        <Route extct path={"/cart"} component={CartList} /> {*追記*}
      </Auth>
    </Switch>
  );
};

export default Router
```

``"/cart"``にルーティングします。

###　動作確認

全体の動作は「完成系イメージ」の通りです。

## さいごに
今回の要点をおさらいすると、

- リスト表示を実装する際は、``map``メソッドでリストアイテム用コンポーネントをイテレートする。

以上です！次回、本格的に購入処理を実装していきます。

このような学習内容を日々呟いていますので、よろしければTwitter([@ddpmntcpbr](https://twitter.com/ddpmntcpbr))のフォローもよろしくお願いします。