react-redux-shopping-cart
react-redux-shopping-cart

React redux shopping cart pointers:

  1. The first step is to create a component to show product with its price and add to cart button.
  2. The second step is to create a component to list products.
  3. Next step is to create cart component to manage to add and remove cart functionality.
  4. Finally Actions and Reducers to achieve functionality.

Get product listing:

I like reactjs because everything in it is a component. Let’s get started by creating a small component for a product. After creating component we have created API in express, so you can use that for fetching products from express mock products API which we have created in node express. After getting products from API we need to show it as product list as below.

static getItemsByPage(page) {
    return fetch(server_url + '/getProducts/' + page).then(response => {
        return response.json();
    }).catch(error => {
        return error;
    });
}

Create react single product component:

This is the smallest possible component we have created in shopping cart example in reactjs (i.e Single Product with its name, price and image). It also has add, remove product buttons to add a product into the cart and remove a product from cart. Add/Remove button is the toggle button to add and remove product to/from cart. The single product component is created as below.

 

import React, { Component } from 'react';
import LazyLoad from 'react-lazyload';
import PlaceholderComponent from './Placeholder';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { addToCart, removeFromCart, isInCart } from '../actions';       // This actions are used to manages activities on cart
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import RaisedButton from 'material-ui/RaisedButton';
import { GridList, GridTile } from 'material-ui/GridList';

// Product component
class ProductComponent extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }

    // Toggle addtocart/removefromcart button based on isInCart status
    handleClick(e) {

        if (this.props.isInCart) {
            this.props.removeFromCart(this.props._id);
        } else {
            const item = {
                id: this.props._id,
                name: this.props.name,
                image: this.props.image,
                price: this.props.price
            };
            this.props.addToCart(item);
        }
    }


    /**
     * @param  {} amount
     *
     */
    getDiscount(amount) {
        if (Math.floor(Math.random() * 3)) {
            return amount + Math.floor(Math.random() * 100) + 1;
        } else {
            return;
        }
    }

    render() {
        const mrp = <span>MRP Rs<span className="mrp">{this.props.mrp ? this.props.mrp : ''}</span> <strong> {this.props.price}</strong></span>;
        return (
            <Card>
                <NavLink to={'/product-details/' + this.props._id}>
                    <CardMedia overlay={<CardTitle title={this.props.name} subtitle={mrp} />} >
                        <LazyLoad offset={80} placeholder={<PlaceholderComponent />} >
                            <img className="group list-group-image" src={this.props.image} alt="" />
                        </LazyLoad>
                    </CardMedia>
                </NavLink>
                <CardText>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                Donec mattis pretium massa.
                </CardText>
                <CardActions className="cart-btn">
                    <RaisedButton primary={!this.props.isInCart} secondary={true} className={this.props.isInCart ? 'dangerbutton' : 'blackroundbutton'} onClick={this.handleClick} label={this.props.isInCart ? 'Remove' : 'Add to cart'} />
                </CardActions>
            </Card>
        )
    }
}

// Mapping isInCart state to props to access easily in component
const mapStateToProps = (state, props) => {
    return {
        isInCart: isInCart(state, props)
    }
}

// Mapping dispatch functions to props to access easily
const mapDispatchToProps = (dispatch) => ({
    addToCart: (item) => dispatch(addToCart(item)),
    removeFromCart: (id) => dispatch(removeFromCart(id))
})


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

Create react product listing component:

Shopping cart product list is nothing but the collection of single product components. This component is used to show the list of products with a product name, description, pricing etc as mentioned in single product component. In this example, we have used react material-ui components for user interface purpose. Refer this link to know more about how to integrate React Material UI with React application.  In this section, we have imported single product component and iterate it by the list of product details.

const items = this.props.items.docs.map((item, i) => (
    <Col xs={6} md={3} className="product-component">
        <ProductComponent key={item._id} {...item} />
    </Col>
));
import React, { Component } from 'react';
import LazyLoad from 'react-lazyload';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import _ from 'underscore';
import { loadMoreItems, searchItems, loadItems } from '../actions';
import ProductComponent from './Product';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
var Spinner = require('react-spinkit');
import AlertComponent from './Alert.component';
import { TextField, Paper } from 'material-ui';
import { Grid, Row, Col } from 'react-flexbox-grid';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';

class ProductsComponent extends React.Component {
    constructor(props) {
        super(props);
        this.getProducts = this.getProducts.bind(this);
        this.handleChange = this.handleChange.bind(this);

        // Underscore debounce to manage search product functionality
        this.searchDebounce = _.debounce(function (keyword) {
            if (keyword) {
                this.props.dispatch(searchItems(keyword));
            } else {
                this.props.dispatch(loadItems());
            }
        }, 500);
    }

    // Init point to load data and attach events
    componentDidMount() {
        this.props.dispatch(loadItems());
        window.addEventListener("scroll", _.debounce((e) => this.getProducts(e), 100));
    }

    // Fetching products on scrolling using pagination(infinite scroll)
    getProducts() {
        var self = this;
        const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
        const body = document.body;
        const html = document.documentElement;
        const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
        const windowBottom = windowHeight + window.pageYOffset;
        console.log((windowBottom / docHeight) * 100);


        if ((windowBottom / docHeight) * 100 >= 80 && !this.props.items.isSearch && !this.props.items.loading && this.props.items.isMoreItems) {
            this.props.dispatch(loadMoreItems(this.props.items.page))
        } else {
            console.log('Not Bottom');
        }
    }

    // Underscore debounce to manage search product functionality
    handleChange(event) {
        this.searchDebounce(event.target.value);
    }

    render() {

        const items = this.props.items.docs.map((item, i) => (
            <Col xs={6} md={3} className="product-component">
                <ProductComponent key={item._id} {...item} />
            </Col>
        ));

        const alert = this.props.items.message ? <AlertComponent message={this.props.items.message} /> : null;
        const loading = this.props.items.loading ? <div className="item  col-xs-12 col-lg-12 spinner-section">
            <Spinner name="ball-pulse-rise" />
        </div> : null;

        return (
            <section>
                <Card className="product-section">
                    <Grid fluid>
                        <Row>
                            <Col xs={12} md={6} className="search-bar">
                                <TextField onChange={this.handleChange} hintText="Search" fullWidth={true} />
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12} md={12} className="no-padding">
                                <div>
                                    {items}
                                </div>
                            </Col>
                        </Row>
                    </Grid>
                </Card>
            </section>
        )
    }
}

// Mapping state and dispatch functions to props
ProductsComponent.propTypes = {
    items: PropTypes.object.isRequired
};

function mapStateToProps(state, ownProps) {
    return {
        items: state.items
    };
}

export default connect(mapStateToProps)(ProductsComponent);

Create shopping cart component:

Shopping cart component is most important part of this example. This component will track all the added and removed product details. Products details such as price and the total amount of all products with all products count which are added to cart. This will manages by addToCart, remove from cart, getCart actions which are shown below.

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import { getCart } from '../actions';
import { connect } from 'react-redux';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import FloatingActionButton from 'material-ui/FloatingActionButton';

const style = {
    marginRight: 20,
};

// Cart component show added items with count of items
class CartComponent extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <FloatingActionButton className="cart-view" style={style}>
                <div className="cart-section">
                    <div className="head-price">
                        <h3 className="">
                            <strong>{this.props.getCart.items ? 'Rs ' + this.props.getCart.total : '0 in cart'}</strong>
                        </h3>
                        <h5 className="">{this.props.getCart.items ? this.props.getCart.items + ' item selected' : ''}</h5>
                    </div>
                    <NavLink to="/" activeClassName="active">
                        <div className="add-to-cart">
                            <img src={require('../images/shopping_icon.png')} className="img-responsive" />
                        </div>
                    </NavLink>
                </div>
            </FloatingActionButton>

        );
    }
}

const mapStateToProps = (state, props) => {
    return {
        getCart: getCart(state.cart, props)     // It it give you all information regarding items in cart
    }
}


export default connect(mapStateToProps)(CartComponent);

Actions to be used in react shopping cart to fetch product list:

In this example, we have created these actions to fetch products from express API. These actions also used for loading products, add to cart, remove from cart, check element is in the cart, get cart product details etc. The code is self-explanatory and easy to understand. Actions

import * as actionType from './ActionType';
import _ from 'underscore';

import itemApi from '../api/itemApi';
/**
 * loadHomeItems:
 * To load home product according to catogory
 */
export function loadHomeItems() {
	return function (dispatch) {
		dispatch(requestItems());
		return itemApi.getHomeItems().then(items => {
			var state = items.docs;
			dispatch(loadHomeItemsSuccess(state));
		}).catch(error => {
			throw (error);
		});
	};
}

/**
 * @param  {} items
 */
export function loadHomeItemsSuccess(items) {
	return { type: actionType.LOAD_HOME_PRODUCTS, items };
}

/**
 * To fetch items from api
 */
export function loadItems() {
	return function (dispatch) {
		dispatch(requestItems());
		return itemApi.getItems().then(items => {
			var state = items;
			dispatch(loadItemsSuccess(state));
		}).catch(error => {
			throw (error);
		});
	};
}

/**
 * @param  {} items
 */
export function loadItemsSuccess(items) {
	return { type: actionType.LOAD_ITEMS_SUCCESS, items };
}

/**
 * @param  {} page
 * Load more items for pagination i.e infinite scroll
 */
export function loadMoreItems(page) {
	return function (dispatch) {
		dispatch(requestItems());
		return itemApi.getItemsByPage(page).then(items => {
			var state = items
			dispatch(loadNextItems(state));
		}).catch(error => {
			throw (error);
		});
	};
}

/**
 * @param  {} items
 *
 */
export function loadNextItems(items) {
	return { type: actionType.LOAD_NEXT_ITEMS, items };
}

/**
 * @param  {} keyword
 * Search product api call
 */
export function searchItems(keyword) {
	return function (dispatch) {
		return itemApi.searchItems(keyword).then(items => {
			var state = items
			dispatch(loadSearchedItems(state));
		}).catch(error => {
			throw (error);
		});
	};
}

/**
 * @param  {} items
 */
export function loadSearchedItems(items) {
	return { type: actionType.LOAD_SEARCHED_ITEMS, items };
}

/**
 * To show api progress
 */
export function requestItems() {
	return { type: actionType.REQUEST_ITEMS, items: {} };
}

/**
 * @param  {} item
 * To add item into cart
 */
export function addToCart(item) {
	return { type: actionType.ADD_TO_CART, item };
}

/**
 * @param  {} id
 * To remove item from cart
 */
export function removeFromCart(id) {
	return { type: actionType.REMOVE_FROM_CART, id };
}

/**
 * @param  {} state
 * @param  {} props
 * To check whether item is in cart or not
 */
export function isInCart(state, props) {
	var cartItems = JSON.parse(localStorage.getItem('cart'));
	return _.some(cartItems, function (item) {
		return item.id == props._id;
	});
}

/**
 * @param  {} state
 * @param  {} props
 * It store information of cart items with quantity and total price
 */
export function getCart(state, props) {
	var cartItems = JSON.parse(localStorage.getItem('cart'));
	var total = 0.00;
	_.each(cartItems, function (item) {
		total += item.price;
	});
	return {
		total: total.toFixed(2),
		items: cartItems ? cartItems.length : 0
	};
}

/**
 * @param  {} id
 * @param  {} props
 * To get particular product information from api
 */
export function getProductDetails(id, props) {
	return function (dispatch) {
		dispatch(requestItems());
		return itemApi.getItemById(id).then(item => {
			dispatch(loadProductDetails(item));
		}).catch(error => {
			throw (error);
		});
	};
}

/**
 * @param  {} item
 * To get particular product information from api
 */
export function loadProductDetails(item) {
	return { type: actionType.LOAD_PRODUCT_DETAILS, item };
}

React Reducers to update state according to products and shopping cart:

React Reducers will manage all the operations perform for cart functionality such as add to cart, remove from cart, and calculating total price and count of items. If you have any doubt regarding react reducers then follow this link. React Reducers

import * as types from '../actions/ActionType';
import defaultState from './defaultState';
import _ from 'underscore';

/**
 * @param  {} state=defaultState.cart
 * @param  {} action
 */
export default function cartReducer(state = defaultState.cart, action) {
    switch (action.type) {
        case types.ADD_TO_CART:
            var cartState = JSON.parse(localStorage.getItem('cart'));
            cartState = cartState ? cartState : [];
            localStorage.setItem('cart', JSON.stringify([...cartState, action.item]));
            return {
                items: JSON.parse(localStorage.getItem('cart'))
            };
        case types.REMOVE_FROM_CART:
            var cartData = JSON.parse(localStorage.getItem('cart'));
            state = _.filter(cartData, function (item) {
                return item.id !== action.id
            });
            localStorage.setItem('cart', JSON.stringify(state));
            return {
                items : state
            };
        default:
            return state;
    }
}

Conclusion:

Finally, we have created fully working react redux shopping cart functionality. If you have any problem understanding above code snippet then just comment. If you want to play with it just follow below link for GitHub source code.

other tutorials on react

React redux material shopping cart demo with a dynamic material theme: Demo!!!

Source Code: Github Repo

 

LEAVE A REPLY

Please enter your comment!
Please enter your name here