React-Redux Smoothie E-commerce Site
Published: 2021-04-30 17:54:43

![Screen Shot 2021-05-07 at 10.41.43 AM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccbk4ibpye0y2dj8znwj.png)

For my final React-Redux project for Flatiron's Software Engineering bootcamp, I created a demo e-commerce site for a fake smoothie shop. The main features are the Build A Smoothie and cart features. In this blog post, I'm going to demo the code that makes up my cart functionality. It's a very barebones shopping cart, which hopefully will be helpful for anyone trying to quickly throw together a cart feature.

To start off, I broke down my shopping cart into 5 different components: 
![Screen Shot 2021-05-07 at 2.46.21 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5m3lq590drilpzkd4otb.png)
 
I also created a redux cart reducer to handle my actions, and mapped my redux state and dispatch actions to a component called CartContainer. Within my CartContainer, I passed redux state/dispatch actions to the appropriate components: 
```
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`, `CartTotal`, and `CheckoutButton` simply renders buttons and data from my global store. 
![Screen Shot 2021-05-07 at 2.51.22 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwm7ta9zdb7pda4jbgtu.png)

Within my `CheckoutContainer`, I created a very standard HTML form to collect checkout information: 
```
                    <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 >

```
The `CheckoutForm` component keeps track of it's own state to pass along to the global redux store which is ultimately what gets POSTed to the database upon checkout finalization. Card information is commented out as I didn't actually want to collect any real card information since this is a fake shop. 
```
    state = {
        items: this.props.items, 
        totalPrice: this.props.totalPrice, 
        customerName: '', 
        address: '', 
        // cardNumber: '', 
        // cardExp: '',
        // cardSecurityNum: '',
        note: '', 
        message: ''
    }
```
I mapped my checkout dispatch functions to my `CheckoutForm` container and utilized them within my submit event handlers: 
```
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"})
        }
    }
```
 My checkout POST action utilizes Thunk to incorporate my dispatches. 
```
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})
            })
        }
}
```
And my reducer handles saving this checkout item to the global store. 
```
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

    }

}
```

As far as my backend, things are pretty simple. My backend is a Rails api. My model relationships that pertain to the cart functionality are very simple: 
```
class Order < ApplicationRecord
    has_many :products

```

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

I save Products to the database at the same time as Orders, as there was no real reason for my to be making extra fetches to the database when creating smoothie prodcuts on the frontend until they needed to be associated with an order. My create method looks like this: 
```
  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
```

I created the class method `add_ingredients` to help create Prodcuts alongside Orders: 
```
    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
```
And that's it! Order's are persisted to the databases along with their Products (smoothies) and the ingredients that make them up. Thanks for reading!