npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-shopping-cart-starter-kit

v0.1.0

Published

Reusable shopping cart component for React with no extra fluff.

Downloads

12

Readme

React Shopping Cart Starter Kit

This component comes with no batteries included, but allows for a great deal of flexibility. It was initially designed for shopping cart type of functionality and product collections (i.e., creating and editing orders), but is likely to be applicable in other contexts where a selection of some kind is involved.

Installation

$ npm install react-shopping-cart-starter-kit

Examples

The following third-party components and assets are used in the examples: React-Bootstrap, Bootstrap, Griddle (griddle-react), React DnD, Font Awesome, and the Lato Font.

Bootstrap + Griddle example

This example also shows how to implement notifications using the various onItem* callbacks.

Bootstrap example

Drag and Drop example

Add products to the cart by dragging the product thumbnail to the drop area.

Drag and Drop example

How to use this component

Preparation

Assign a unique id to each item used in your application. For demonstration, we will use the following key-value object with a catalog of five products in subsequent examples.

const myProducts = {
  "product-1" : { "Name" : "Canned Unicorn Meat",   "Price" : "9.99"  },
  "product-2" : { "Name" : "Disappearing Ink Pen",  "Price" : "14.99" },
  "product-3" : { "Name" : "USB Rocket Launcher",   "Price" : "29.99" },
  "product-4" : { "Name" : "Airzooka Air Gun",      "Price" : "29.99" },
  "product-5" : { "Name" : "Star Trek Paper Clips", "Price" : "19.99" }
}

Hello, World!

To get started, we print out the products in a list, pass the column names to the cart component, and implement an onClick handler which adds the selected item to the cart by calling addItem.

import React from 'react'
import Cart  from 'react-shopping-cart-starter-kit'

const myProducts = {
  "product-1" : { "Name" : "Canned Unicorn Meat",   "Price" : "9.99"  },
  "product-2" : { "Name" : "Disappearing Ink Pen",  "Price" : "14.99" },
  "product-3" : { "Name" : "USB Rocket Launcher",   "Price" : "29.99" },
  "product-4" : { "Name" : "Airzooka Air Gun",      "Price" : "29.99" },
  "product-5" : { "Name" : "Star Trek Paper Clips", "Price" : "19.99" }
}

const MyComponent = React.createClass({
    submit() {
        const selection = this.refs.cart.getSelection()
        alert(JSON.stringify(selection))
    },
    addItem(key) {
        this.refs.cart.addItem(key, 1, this.props.products[key])
    },
    render() {
        const products = this.props.products
        return (
            <div>
                <h4>Products</h4>
                <ul>
                    {Object.keys(products).map(key => {
                        return (
                            <li key={key}>
                                <a href='#' onClick={() => this.addItem(key)}>
                                    {products[key]['Name']}
                                </a>
                            </li>
                        )
                    })}
                </ul>
                <hr />
                <Cart ref='cart' columns={['Name', 'Price']} />
                <hr />
                <button onClick={this.submit}>
                    Submit
                </button>
            </div>
        )
    }
})

React.render(
    <MyComponent products={myProducts} />,
    document.getElementById('main')
)

Improvements to Hello, World!

Next, we'll implement a row iterator to sum up the order total. It will appear in the table footer (See 'Customization' for details).

<Cart iterator={this.rowIterator} ref='cart' columns={['Name', 'Price']} />

This function is first called once to allow initialization, and then for each item in the cart. The object we return is being passed on as an argument to the subsequent call, together with the row item.

    rowIterator(context, row) {
        if (!context) {
            /* Initialization call */
            return {
                total : 0
            }
        } else {
            /* Invoked once for each row */
            const price = Number(row.data['Price'])
            return {
                total : context.total + row.quantity * price
            }
        }
    },

Finally, we'd like to have the submit button disappear when nothing is present in the cart. To achieve this, we introduce a canSubmit flag.

        <Cart 
          ref      = 'cart'
          onChange = {this.cartChanged}
          iterator = {this.rowIterator}
          columns  = {['Name', 'Price']} />
        <hr />
        {this.state.canSubmit && ( 
            <button onClick={this.submit}>
                Submit
            </button>
        )}

Here are the implementations for cartChanged and getInitialState, which we add to MyComponent.

    getInitialState() {
        return {
            canSubmit : false
        }
    },
    cartChanged() {
        this.setState({
            canSubmit : !this.refs.cart.isEmpty()
        })
    },

Editing an existing selection of items

Up to this point, we have assumed that the cart is initially empty. When working with an existing order or selection, we can provide an array of items to the cart's selection prop.

        <Cart 
          /* ... as before ... */
          selection = {[
              {
                  id       : 'product-2',
                  quantity : 15,
                  data     : myProducts['product-2']
              },
              {
                  id       : 'product-3',
                  quantity : 1,
                  data     : myProducts['product-3']
              }
          ]} 

To allow the user to revert any changes back to the order's initial state, we add a button that triggers the cart's reset method.

    <button onClick={this.undoChanges}>
        Undo changes
    </button>

To make the submit button appear in edit mode, we add a call to cartChanged after the component has mounted.

    undoChanges() {
        this.refs.cart.reset()
    },
    componentDidMount() {
        this.cartChanged()
    },

To change how the component renders the cart's contents, implement the containerComponent and/or rowComponent props. (See Customization)

Props

Required

| Property | Type | Description | | ---------------- | ------------------------ | -------------------------------------------------------- | | columns | Array | The columns used in the table of items currently in the cart. Items added to the cart should have keys matching the entries of this array. |

Optional

| Property | Type | Description | Default | | ---------------- | ------------------------ | ---------------------------------------------------------------------------------- | ----------------------- | | items | Object | Normally, you pass an item's data with the call to addItem. As an alternative, you can provide an object here, mapping each key to an object with the item's attributes. |                                   | selection | Array | Initial selection. (Used when editing an existing order or selection of items). | [] | | onItemAdded | Function | Called when an item is added to the cart. | () => {} | | onItemRemoved | Function | Called when an item is removed from the cart. | () => {} | | onItemQtyChanged | Function | Called when an item's quantity has changed. | () => {} | | onChange | Function | Called when the state of the component changes. (You may want to implement this callback to toggle the visibility of a submit button, based on whether the cart is empty or not.) | () => {} | | iterator | Function | A function used to pass state between rows. The real raison d'être for this function is to sum up the price of each product in an order and output a total in the footer. | () => { ... } | | containerComponent | Component | A custom container component. | See 'Customization' | | rowComponent | Component | A custom row component. | See 'Customization' | | tableClassName | String | The CSS class name to apply to the table element. Whether this value is actually used or not depends on the implementation of containerComponent. | | | cartEmptyMessage | Node | A message shown when the cart is empty. | 'The cart is empty.' |

Initial data

When editing an existing selection, use the selection prop to pass the collection as an array in the following format.

const orderData = [
  {
    "id"       : "item-1",
    "quantity" : 2,
    "data"     : { "name": "Canned Unicorn Meat", "price" : "9.99" }
  },
  {
    "id"       : "item-2",
    "quantity" : 1,
    "data"     : { "name": "Disappearing Ink Pen", "price" : "14.99" }
  }
]

API

addItem(key, quantity, item)


To add an item to the cart, provide its id, a quantity, and the item itself. (The third argument may not be required if you have previously supplied an object to the component's items props.)

cart.addItem('product-1', 1, myProducts['product-1'])

If an item with the given id already exists in the cart, no new item is inserted. Instead, the quantity is adjusted accordingly for the existing entry.

cart.addItem('product-1', 1, myProducts['product-1'])
cart.addItem('product-1', 1)

cart.getSelection()

[
  {
    "id"       : "product-1",
    "quantity" : 2,
    "data"     : { "name": "Canned Unicorn Meat", "price" : "9.99" }
  }
]

emptyCart()


Clears the cart.

reset()


Does the same as emptyCart(), unless you specify the selection prop, in which case the initial selection will be restored. That is, when editing an existing order, this method will revert the cart back to a state consistent with the order being edited.

getSelection()


Return the current selection.

[
  {
    "id"       : "item-1",
    "quantity" : 2,
    "data"     : { "name": "Canned Unicorn Meat", "price" : "9.99" }
  },
  {
    "id"       : "item-2",
    "quantity" : 1,
    "data"     : { "name": "Disappearing Ink Pen", "price" : "14.99" }
  }
]

isEmpty()


Returns true if the cart is empty, otherwise false.

removeItem(index)


In a typical implementation, it is not necessary to call this method directly. Instead, use this.props.removeItem from within the row component.

updateQuantity(index, quantity)


In a typical implementation, it is not necessary to call this method directly. Instead, use this.props.setItemQty from within the row component.

Customization

To gain more control over how the cart is rendered (beyond what can be done with CSS), it is possible to implement a custom row and/or container component.

    <Cart 
      rowComponent       = {MyRowComponent}
      containerComponent = {MyContainerComponent}
      />

Row component

The row component renders individual rows. The default implementation uses a <table> element at the top, and thus row data appears within a <tr> tag, however these could be pretty much anything, as long as the node tree follows the normal JSX rules.

Props

| Property | Type | Description | | ---------------- | ------------------------ | ---------------------------------------------------------------------------------- | | item | Object | The row item (see below). | columns | Array | The array of column names. | removeItem | Function | Callback to invoke to remove the item from the cart. | setItemQty | Function | Callback to invoke to change the selected quantity of an item.

The item object

This object holds the id, current quantity of the item, and its properties (the data you specified when calling addItem). The format is as follows.

  {
    "id"       : "item-1",
    "quantity" : 2,
    "data"     : { "name": "Canned Unicorn Meat", "price" : "9.99" }
  }

Default implementation

const RowComponent = React.createClass({
    handleChange(event) {
        const value = event.target.value
        if (!isNaN(value) && value > 0) {
            this.props.setItemQty(value)
        }
    },
    render() {
        return (
            <tr>
                {this.props.columns.map(column => {
                    return (
                        <td key={column}>
                            {this.props.item.data[column]}
                        </td>
                    )
                })}
                <td>
                    <input
                      style    = {{textAlign: 'right', width: '100px'}}
                      type     = 'number'
                      value    = {this.props.item.quantity}
                      onChange = {this.handleChange} />
                </td>
                <td>
                    <button 
                      onClick  = {this.props.removeItem}>
                        Remove
                    </button>
                </td>
            </tr>
        )
    }
})

Container component

This component is responsible for rendering the container in which the cart's contents appear. For more creative layouts, you may want to use something other than a <table> here.

Props

| Property | Type | Description | | ---------------- | ------------------------ | ---------------------------------------------------------------------------------- | | columns | Array | The array of column names. | tableClassName | String | A CSS class name to be applied to the table element. (May be ignored by custom implementations.) | body | Node | The node tree generated by the row component. Typically rendered in the body portion of a table. | context | Object | A state object generated by the rowIterator, if one is used.

Default implementation

const ContainerComponent = React.createClass({
    render() {
        return (
            <table className={this.props.tableClassName}>
                <thead>
                    <tr>
                        {this.props.columns.map(column => {
                            return (
                                <th key={column}>
                                    {column}
                                </th>
                            )
                        })}
                        <th>
                            Quantity
                        </th>
                        <th />
                    </tr>
                </thead>
                <tbody>
                    {this.props.body}
                </tbody>
                {this.props.context.total && (
                    <tfoot>
                        <tr>
                            <td colSpan={this.props.columns.length-1} style={{textAlign: 'right'}}>
                                <strong>Total:</strong>
                            </td>
                            <td colSpan={3}>
                                {this.props.context.total.toFixed(2)}
                            </td>
                        </tr>
                    </tfoot>
                )}
            </table>
        )
    }
})

Bootstrap styles

In this example, we create a row component to "bootstrapify" the table. See examples for a more complete implementation.

main.js
const BootstrapRowComponent = React.createClass({
    handleChange(event) {
        const value = event.target.value
        if (!isNaN(value) && value > 0) {
            this.props.setItemQty(value)
        }
    },
    increment() {
        const value = this.props.item.quantity + 1
        this.props.setItemQty(value)
    },
    decrement() {
        const value = this.props.item.quantity - 1
        if (value) {
            this.props.setItemQty(value)
        }
    },
    render() {
        return (
            <tr>
                {this.props.columns.map(column => {
                    return (
                        <td key={column}>
                            {this.props.item.data[column]}
                        </td>
                    )
                })}
                <td>
                    <div className='input-group input-group-sm' style={{maxWidth: '110px'}}>
                        <span className='input-group-btn'>
                            <button
                              className = 'btn btn-default btn-sm'
                              onClick   = {this.decrement}>
                                <i className='fa fa-minus' />
                            </button>
                        </span>
                        <input
                          style     = {{textAlign: 'right'}}
                          className = 'form-control'
                          type      = 'text'
                          value     = {this.props.item.quantity}
                          onChange  = {this.handleChange} />
                        <span className='input-group-btn'>
                            <button
                              onClick   = {this.increment}
                              className = 'btn btn-default btn-sm'>
                                <i className='fa fa-plus' /> 
                            </button>
                        </span>
                    </div>
                </td>
                <td>
                    <button 
                      className = 'btn btn-default btn-sm'
                      onClick   = {this.props.removeItem}>
                        <i className='fa fa-remove' /> 
                    </button>
                </td>
            </tr>
        )
    }
})

const MyComponent = React.createClass({

    /*
        Code left out for brevity. Copy this portion from previous examples.
     */

    render() {
        const products = this.props.products
        return (
            <div>
                <h4>Products</h4>
                <ul>
                    {Object.keys(products).map(key => {
                        return (
                            <li key={key}>
                                <a href='#' onClick={() => this.addItem(key)}>
                                    {products[key]['Name']}
                                </a>
                            </li>
                        )
                    })}
                </ul>
                <hr />
                <Cart 
                  ref            = 'cart'
                  tableClassName = 'table'
                  rowComponent   = {BootstrapRowComponent}
                  onChange       = {this.cartChanged}
                  iterator       = {this.rowIterator}
                  columns        = {['Name', 'Price']} />
                <hr />
                {this.state.canSubmit && ( 
                    <button 
                      className = 'btn btn-block btn-default btn-primary btn-sm'
                      onClick   = {this.submit}>
                        Submit
                    </button>
                )}
            </div>
        )
    }
})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
    <title></title>
</head>
<body>
    <div id="main"></div>
    <script src="bundle.js"></script>
</body>
</html>

License

This software is provided under the terms and conditions of the BSD License.