├── README.md ├── code ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ │ ├── Nav.js │ │ ├── ProductList.js │ │ ├── ProductRow.js │ │ └── cart │ │ │ ├── Cart.js │ │ │ ├── CartCheckoutRow.js │ │ │ ├── CartProductList.js │ │ │ ├── CartProductRow.js │ │ │ ├── CartTotalRow.js │ │ │ └── RecommendedProduct.js │ ├── context │ │ └── CartContext.js │ ├── index.css │ ├── index.js │ ├── lib │ │ └── Commerce.js │ ├── logo.svg │ ├── logo192.png │ ├── serviceWorker.js │ └── setupTests.js └── yarn.lock └── images ├── cart_button.png ├── cart_recommended_product.png ├── cart_with_products.png ├── product_list.png └── recommended_add.png /README.md: -------------------------------------------------------------------------------- 1 | # Adding products to the cart with React.js and Commerce.js 2 | 3 | ## Overview 4 | 5 | In the previous guide, [Listing products with React.js and Commerce.js](https://github.com/robingram/commercejs-list-products-react) we created a simple list of the products we'd created in the Chec Dashboard. Next we're going to look at how to display the contents of the cart and add those products to it. Finally, we'll add simulated recommended products to the cart to encourage further purchases. 6 | 7 | In this guide we'll: 8 | 9 | * Retrieve a cart from Commerce.js and make it available to our app 10 | * Add React components to display the contents of the cart 11 | * Extend the existing products list to allow products to be added to the cart 12 | * Display simulated recommended products products in the cart 13 | 14 | You can see a [live demo](http://commercejs-add-to-cart.s3-website-ap-southeast-2.amazonaws.com/) of what we'll produce. 15 | 16 | ## Requirements 17 | 18 | As with the [previous guide](https://github.com/robingram/commercejs-list-products-react), you'll need a [Chec dashboard](https://authorize.chec.io/login) account, Node.js and npm/yarn and a code editor. 19 | 20 | Some basic knowledge of HTML, JavaScript and Bootstrap would be helpful and you'll need to be comfortable using the command line on your computer. 21 | 22 | ## Setup 23 | 24 | We'll build on the code from the previous guide but since this time we'll be making more use of Bootstrap elements we'll add [reactstrap](https://reactstrap.github.io/) to our app. Also we want nice icons for our cart so we'll add [FontAwesome](https://github.com/FortAwesome/react-fontawesome) too. 25 | 26 | ``` 27 | yarn add reactstrap 28 | yarn add @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome 29 | ``` 30 | 31 | Finally add a little CSS to the end of our `index.css` file that will be used to space out text and icons within buttons. 32 | 33 | *src/index.css* 34 | ``` 35 | .icon-button-text-right { 36 | padding-left: 0.5rem; 37 | } 38 | ``` 39 | 40 | ## Using the Commerce.js cart object 41 | 42 | The Commerce.js API provides a number of endpoints for interacting with [carts](https://commercejs.com/docs/api/#carts) and these are encapsulated in the SDK's `cart` object. 43 | 44 | We'll need to interact with the cart in a number of places within our app: 45 | 46 | * In the `Cart` component to display the products that are in the cart 47 | * In the products list to be able to add products to the cart 48 | 49 | And there are likely to be more places in future as our application grows. Therefore we will manage the state of the cart in our top level `App` component and make it available to any child components that need it. 50 | 51 | In the standard React unidirectional data model state is passed *down* to child components through `props`. In addition, the parent component may also pass down event handler functions to update that state. This works well for small components with few children but tends to break down in situations like this where we want to access the cart in multiple children across our component hierarchy. Using the standard method we would have to pass the cart state and event handlers through multiple intermediate components that don't need to be concerned with the cart state at all. 52 | 53 | To get around this we'll be using React [Contexts](https://reactjs.org/docs/context.html). A Context allows state that is managed in a high level component to be published via a context *provider*. Then, any child components that need to use that state can declare a *consumer* of the context in order to access the state. 54 | 55 | We'll look at how we set this up for the cart in out `App` component and then build out the other components that will consume the context. 56 | 57 | ### Set up shared components 58 | 59 | We will need to access the context that we create in various places across our app. In addition we will also now need to use the Commerce.js SDK in more than one component so we'll put the initialisaton of these objects into separate files that we can include where necessary. First extract the Commerce.js initialisation by creating the file `src/lib/Commerce.js` with the following contents: 60 | 61 | ``` 62 | import Commerce from '@chec/commerce.js'; 63 | 64 | export const commerce = new Commerce(process.env.REACT_APP_CJS_PUBLICKEY_TEST); 65 | ``` 66 | 67 | Notice that we're now using an *environment variable* (`REACT_APP_CJS_PUBLICKEY_TEST`) to store our API key rather than hard coding it. This is better for security. Check your operating system documentation for how to set environment variables. 68 | 69 | Now set up a file to initialise our context. Create `src/context/CartContext.js` with these lines. 70 | 71 | ``` 72 | import React from 'react'; 73 | 74 | const CartContext = React.createContext({ 75 | cart: {}, 76 | setCart: () => {} 77 | }); 78 | export default CartContext; 79 | ``` 80 | 81 | Here we're creating a Context for our cart with default values of an empty cart and an empty function for updating a cart. we'll replace these defaults when we initialise the context's provider. 82 | 83 | ### Use the context in the `App` component 84 | 85 | Modify the `App` component to retrieve the cart and make it available to its children using the context provider. Update `App.js` to the code below. 86 | 87 | *src/App.js* 88 | ``` 89 | import React, { useEffect, useState } from 'react'; 90 | import './App.css'; 91 | import Nav from './components/Nav'; 92 | import ProductList from './components/ProductList'; 93 | import CartContext from './context/CartContext'; 94 | import { commerce } from './lib/Commerce'; 95 | 96 | function App() { 97 | const [cart, setCart] = useState(); 98 | 99 | useEffect(() => { 100 | commerce.cart.retrieve() 101 | .then(cart => { 102 | setCart(cart); 103 | }) 104 | }, []) 105 | 106 | return ( 107 |
108 | 109 |
116 | ); 117 | } 118 | 119 | export default App; 120 | ``` 121 | 122 | The `Nav` component is new and we'll create that shortly. It will include a button to toggle a modal dialogue that contains the cart itself. First though, let's look at how we're handling the cart state. 123 | 124 | We begin by importing the cart context and Commerce.js SDK from the files we created earlier. Next we use React's `useState` function to create the `cart` state and a function for setting that state. 125 | 126 | ``` 127 | const [cart, setCart] = useState(); 128 | ``` 129 | 130 | Next we retrieve a cart object using the SDK and set it to the `cart` state. We've put this into a `useEffect` call. This can be thought of as the equivalent of `componentDidMount` which we saw in `ProductList` except for use in *functional* components, so this will be called when the component is rendered. 131 | 132 | ``` 133 | useEffect(() => { 134 | commerce.cart.retrieve() 135 | .then(cart => { 136 | setCart(cart); 137 | }) 138 | }, []) 139 | ``` 140 | 141 | Here we're calling the `cart.retrieve` function of Commerce.js. Like most of the SDK functions it returns a promise that we're handling in `then` to set the returned cart object to the component's state. 142 | 143 | Finally we're creating a context provider to make the cart state and its associated update method available to the rest of the app. 144 | 145 | ``` 146 | 147 |