React-Redux 스무디 전자상거래 사이트

9741 단어


Flatiron의 소프트웨어 엔지니어링 부트캠프를 위한 마지막 React-Redux 프로젝트를 위해 가짜 스무디 가게를 위한 데모 전자 상거래 사이트를 만들었습니다. 주요 기능은 Build A Smoothie 및 카트 기능입니다. 이 블로그 게시물에서는 카트 기능을 구성하는 코드를 시연할 것입니다. 장바구니 기능을 빠르게 함께 사용하려는 모든 사람에게 도움이 되었으면 하는 매우 기본적인 쇼핑 카트입니다.

우선 장바구니를 5가지 구성 요소로 분류했습니다.


또한 내 작업을 처리하기 위해 redux 카트 리듀서를 만들고 내 redux 상태를 매핑하고 CartContainer라는 구성 요소에 작업을 발송했습니다. 내 CartContainer 내에서 redux state/dispatch 작업을 적절한 구성 요소에 전달했습니다.

import React, { Component } from 'react';
import CartList from '../components/cart/CartList'
import { connect } from 'react-redux'
import CartTotal from '../components/cart/CartTotal'
import CheckoutButton from '../components/cart/CheckoutButton'
import { Container } from 'react-bootstrap'
import { removeCartItem } from '../actions/cartActions.js'
class CartContainer extends Component {

    render() {
        return (
            <Container> 
                <div className="body-wrapper">
                    <div className="inner-wrapper">
                        <div>
                            <CartList removeCartItem={this.props.removeCartItem} items={this.props.items} />
                            <CartTotal cartTotal={this.props.cartTotal} />
                            <CheckoutButton items={this.props.items} totalPrice={this.props.cartTotal} />
                        </div>
                    </div>
                </div>
            </Container>
        );
    }
}

const mapStateToProps = (state) => ({
    items: state.cartReducer.cartItems,
    ingredientIds: state.cartReducer.cartItems.ingredientIds,
    cartTotal: state.cartReducer.cartTotal
})

const mapDispatchToProps = dispatch => ({
    removeCartItem: (id) => dispatch(removeCartItem(id)),
})

export default connect(mapStateToProps, mapDispatchToProps)(CartContainer);


My CartList , CartItem , CartTotalCheckoutButton 는 단순히 내 글로벌 스토어의 버튼과 데이터를 렌더링합니다.


CheckoutContainer 내에서 결제 정보를 수집하기 위해 매우 표준적인 HTML 양식을 만들었습니다.

                    <form className="checkout-form">
                        <label>Please Enter Checkout Details</label><br></br>
                    <Row>
                        <br></br>
                        <Col>
                            <label htmlFor="customerName">* Name:</label><br></br>
                            <input id="customerName" type="text" name="customerName" onChange={this.handleChange} /><br></br>

                            <label htmlFor="address">* Address:</label><br></br>
                            <textarea id="address" type="textarea" name="address" onChange={this.handleChange} /><br></br>

                            <label htmlFor="note">Customer Note:</label><br></br>
                            <input id="note" type="text" name="note" onChange={this.handleChange} /><br></br>
                        </Col>
                        <Col>
                            <label htmlFor="cardnum">* Card Number:</label>
                            <input id="cardnum" type="text" name="cardNumber" onChange={this.handleChange} /><br></br>
                            <label htmlFor="cardexp">* Expiration Date (MM/YY):</label>
                            <input id="cardexp" type="text" name="cardExp" onChange={this.handleChange} /><br></br>

                            <label htmlFor="cardsec">* Security Code:</label>
                            <input id="cardsec" type="password" name="cardSecurityNum" onChange={this.handleChange} /><br></br>
                            <br></br>
                            <Button variant="success" onClick={(e) => this.handleSubmit(e)} >Submit Order</Button>
                        </Col>

                    </Row>

                    </form >


CheckoutForm 구성 요소는 궁극적으로 체크아웃 완료 시 데이터베이스에 게시되는 글로벌 redux 스토어로 전달하기 위해 자체 상태를 추적합니다. 카드정보는 가맹점이라 사실 진짜 카드정보를 수집하고 싶지 않아서 주석 처리합니다.

    state = {
        items: this.props.items, 
        totalPrice: this.props.totalPrice, 
        customerName: '', 
        address: '', 
        // cardNumber: '', 
        // cardExp: '',
        // cardSecurityNum: '',
        note: '', 
        message: ''
    }


결제 디스패치 기능을 내 CheckoutForm 컨테이너에 매핑하고 제출 이벤트 핸들러 내에서 활용했습니다.

const mapDispatchToProps = dispatch => ({
    checkout: (data) => dispatch(checkout(data)),
    emptyCart: () => dispatch(emptyCart())
})



    handleChange = (e) => {
        this.setState((prevState) => ({
            ...prevState, 
            [e.target.name]: e.target.value, 
        }))
    }

    handleSubmit = (e) => {
        e.preventDefault()
        if (this.state.items.length > 0 && this.state.customerName !=='' && this.setState.address !== '') {
            this.props.checkout(this.state)
            this.props.emptyCart()
            this.setState({message: "Your order is on the way!"})
        } else {
            this.setState({message: "Please fill out all required fields"})
        }
    }


체크아웃 POST 작업은 Thunk를 활용하여 내 디스패치를 ​​통합합니다.

export const checkout = (data) => {

        return (dispatch) => {
            dispatch({type: 'LOADING_POST'})

            return fetch('https://boiling-earth-59543.herokuapp.com/orders', {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                },
                body: JSON.stringify(humps.decamelizeKeys({order: data}))
            })
            .then(resp => resp.json())
            .then(json => {
                console.log('this is the posted checkout obj', json)
                dispatch({type: 'CHECKOUT', payload: data})
            })
        }
}


그리고 내 감속기는 이 체크아웃 항목을 글로벌 스토어에 저장하는 작업을 처리합니다.

export default function cartReducer(state=  {
    order: []}, action) {

    switch (action.type) {

        case 'CHECKOUT': 

        const newOrder = {
            ingredientIds: action.payload.items,
            totalPrice: action.payload.totalPrice, 
            customerName: action.payload.customerName, 
            address: action.payload.address, 
            cardNumber: action.payload.cardNumber, 
            cardExp: action.payload.cardExp,
            cardSecurityNum: action.payload.cardSecurityNum,
            note: action.payload.note
        }

        return {
            order: [...state.order, newOrder]
        }


        default: 
            return state

    }

}


내 백엔드에 관한 한 상황은 매우 간단합니다. 내 백엔드는 Rails API입니다. 카트 기능과 관련된 내 모델 관계는 매우 간단합니다.

class Order < ApplicationRecord
    has_many :products




class Product < ApplicationRecord
    has_many :product_ingredients
    has_many :ingredients, through: :product_ingredients
    belongs_to :order


주문과 연결해야 할 때까지 프론트엔드에서 스무디 제품을 생성할 때 데이터베이스에 추가 가져오기를 수행할 실질적인 이유가 없었기 때문에 주문과 동시에 제품을 데이터베이스에 저장합니다. 내 생성 방법은 다음과 같습니다.

  def create
    @order = Order.create(order_params)
    Product.add_ingredients(params[:order][:items], @order)
    if @order.save
      render json: @order, status: :created, location: @order
    else
      render json: @order.errors, status: :unprocessable_entity
    end
  end


주문과 함께 Prodcuts를 만드는 데 도움이 되는 클래스 메서드add_ingredients를 만들었습니다.

    def self.add_ingredients(items, order)
        items.each do |item|
            product = Product.create
            item[:ingredient_ids].each do |id|
                ingredient = Ingredient.find_by(id: id)
                product.ingredients << ingredient
                product.order_id = order.id
                product.save
            end
        end
    end


그리고 그게 다야! 주문은 제품(스무디) 및 이를 구성하는 재료와 함께 데이터베이스에 유지됩니다. 읽어 주셔서 감사합니다!

좋은 웹페이지 즐겨찾기