JavaScript
React
redux

Reduxのサンプルのショッピングカートをステップ・バイ・ステップで Part.4

Reduxのサンプルのショッピングカートをステップ・バイ・ステップで Part.3のつづき。

1.カートに合計額を表示する

カート内の合計額を算出する getTotal関数を追加

reducers\index.js追加部分
export const getTotal = state =>{
    return getAddedIds(state)
    .reduce(
        (total,id) => total +getProduct(state,id).price*getQuantity(state,id),
        0
    ).toFixed();
}

CartContainerコンテナにプロップスを追加

getTotalで算出した合計額をtotalプロップスとしてコンテナに渡す。コンテナ内部ではCartコンポーネントにtotalプロップスを渡している。

containers\CartContainer.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { getCartProducts , getTotal} from '../reducers'
import Cart from '../components/Cart'

const CartContainer = ({ products,total}) => (
  <Cart
    products={products}
    total={total}
     />
)

CartContainer.propTypes = {
  products: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    title: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    quantity: PropTypes.number.isRequired
  })).isRequired,
  total:PropTypes.string.isRequired
}
const mapStateToProps = (state) => ({
  products: getCartProducts(state),
  total:getTotal(state)
})
export default connect(mapStateToProps)(CartContainer)

Cartコンポーネントの編集

CartContainerコンテナから渡されたtotalプロップスに対応させる。合計額表示DOMを追加。

components\Cart.js
import React from 'react'
import PropTypes from 'prop-types'
import Product from './Product'

const Cart  = ({ products,total}) => {
  const hasProducts = products.length > 0
  const nodes = hasProducts ? (
    products.map(product =>
      <Product
        title={product.title}
        price={product.price}
        quantity={product.quantity}
        key={product.id}
      />
    )
  ) : (
    <em>ご希望の商品をカートに追加してください</em>
  )

  return (
    <div className="border border-dark alert alert-secondary rounded p-1 clearfix mx-auto" style={{width:'30em'}}>
      <h5 className="mb-1 text-center">&#x1f6d2;ショッピングカート</h5>
      <div>{nodes}</div>
      <p className="text-white bg-dark text-center" style={{ marginLeft: "20em" }}>合計: &#165;{total}</p>
    </div>
  )
}

Cart.propTypes = {
  products: PropTypes.array,
  total:PropTypes.string
}

export default Cart

実行結果

2018_0614_1157_29.jpg

ソースコード 04-1

2.決済機能を付ける

ほとんど、見た目だけのなんちゃって決済機能。

アクションのtype追加

constants\ActionTypes.js
export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'
export const ADD_TO_CART = 'ADD_TO_CART'
export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST'
export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS'
export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE'

アクションに対応するdispatchをする関数追加

checkout関数を追加。
これはCHECKOUT_REQUESTアクションと、CHECKOUT_SUCCESSアクションをディスパッチする。
shop.buyProducts関数は、内部で無条件に決済成功した場合のアクションをディスパッチしている。
実際は、サーバーからの返答を受け取ってから、成功の場合と失敗の場合の両方に対応すべき。

actions\index.js
import shop from '../api/shop'
import * as types from '../constants/ActionTypes'
import {getTotal} from '../reducers'

//(中略....................)


//実行時、dとgにはdispatchとgetStateが割り当てられる。
//g すなわち getState() は state tree を返す。
export const checkout = products => (d, g) => {
// stateからstate.cartだけ取り出し
    const { cart } = g();
    const paid = getTotal(g()) 
    d({
        type: types.CHECKOUT_REQUEST
    });

    shop.buyProducts(
        products,
        //とりあえずここでは、無条件に成功するようにしておく。
        //このコールバック関数は、サーバー側の決済処理が成功すれば、成功のdispatch
        // 失敗すれば失敗のdispatchをする関数にするべきだろう
        () => {
            d({
                type: types.CHECKOUT_SUCCESS,
                paid,
                cart
            })
        }
        // () => {
        //     dispatch({
        //         type: types.CHECKOUT_FAILURE,
        //         cart
        //     })
        // }
    )
}

cartリデューサを修正

CHECKOUT_REQUEST, CHECKOUT_SUCCESS, CHECKOUT_FAILUREアクションに対応させた。
CHECKOUT_SUCCESSのときは、コンソールに支払い金額を表示する。

reducers\cart.js
import {
    CHECKOUT_REQUEST,
    CHECKOUT_FAILURE,
    CHECKOUT_SUCCESS,
    ADD_TO_CART
} from "../constants/ActionTypes";

//中略....

//cartリデューサの実体
//内部で addedIds, quantityById リデューサをひとまとめにして利用している
const cart = (state = initialState, action) => {
    switch (action.type) {
        case CHECKOUT_REQUEST:
            return initialState;
        case CHECKOUT_FAILURE:
            console.log("決済に失敗しました。");
            return action.cart
        // ADD_TO_CARTはこちら
        case CHECKOUT_SUCCESS:
            console.log("決済が完了しました。");
            console.log("お支払い額:¥"+action.paid);
            return initialState;
        // ADD_TO_CARTはこちら
        default:
            return {
                addedIds: addedIds(state.addedIds, action),
                quantityById: quantityById(state.quantityById, action)
            }
    }
}

export default cart


実行結果

ソースコード 04-2

これで、"Reduxのサンプルのショッピングカートをステップ・バイ・ステップ" の終わり。