If we're fortunate enough, not only will people want to buy one item from our store, they'll want to buy multiple. To do that, we need to provide a shopping cart, which allows us to store what items the customer wants to buy, how many of them, and how much these items will cost.
To do this, we can take advantage of React's built-in state management hooks to make sure we're accurately keeping track of their order.
We will save our cart data in useState that will be used to calculate the subtotal, total items, and total price that our customer has in their cart. During this process, we will move the Stripe Checkout flow out of our Product Cards and into a new checkout section. The product cards will add items to the state that we create.
Instructor: [0:00] We're going to start off with our Space Jelly Shop. We have three products that we are immediately able to buy with Stripe checkout. The current issue is we can only buy one of them and one at a time. To solve this, we can add a shopping cart, where we can store the items that somebody wants to purchase and then let them purchase them all at once.
[0:15] Starting off inside of our code, we need an area where we're going to keep our shopping cart. Starting at the top, we can take our description block. We can clone that. We can add some new sections to it. We can add something like items where, for now, let's say we just have two items.
[0:28] Then after putting a space between there, we can say the total cost. Let's say for now that's $20. Finally, we want somebody to be able to click a button to check out with those items. We're going to add another space. We're going to add a button. We're going say Check out.
[0:44] If we look in the browser, we can see our cart, but it's not super easy to read and this button is a little small. First, on our items, let's add a strong tag around it just so it stands out a little bit more as a label. We can do the same thing with our total cost.
[0:57] Then our button, we can add the same class name that we used for our products. We can add class name = styles button. We can see the update. It's still not perfect, but I think this is good enough for now.
[1:07] Next, we want to create an instance of state where we can actually save our cart state. To do this, we're going to use the useState hook. We're going to import useState from React. Now we can create a new instance from cart and update cart from useState. We can also set a default cartState so that we know what the structure is and that we can use it throughout the app.
[1:28] We're going to create a new constant called defaultCart. We're going to set that equal to an object. Inside that object, we're going to set a property of products that's equal to an empty object. The reason we're using and object for our products is it's easier to create a comparison between an object to another object rather than to arrays.
[1:46] For our current state, we're going to have this object of products where each of the keys is going to be the product ID. Now that we have our default cart defined, we can take that value and pass it right into useState so it starts off with that value.
[1:58] Next, now that we have our cart state, we want to add a function where we can add an item to that cart state. I'm going to define a new function. I'm going to call it addToCart. I'm going to pass in a destructured ID as the argument and set it default to an empty object. When someone calls that function, I'm going to run updateCart. Instead of passing a direct value, I'm going to pass it a function.
[2:18] I'm going to pass it an argument of previous where I'm going to create a new function. I'm going to create a clone of that state. I'm going to say let cartState =, and I'm going to spread that previous state out onto an empty object.
[2:31] Because we actually don't know if the product is currently in our cartState, we want to first check if it exists. We're going to say if our cartState.products, and then as a dynamic value, we're going to pass in our ID.
[2:44] If that exists, we're going to take that object and we're going to set the quantity of it, and we're going to set that equal to the same quantity +1 so it increments it. Otherwise, if it doesn't exist, we going to give an else statement where we're going to set that value to an object both setting the ID of that object and a quantity of 1.
[3:05] Finally, because we're done with our cartState, we're going to return that inside of our function. Now that we have our addToCart function, let's scroll down to our products. When someone clicks our button, instead of initiating checkout, we want to add an item to the cart.
[3:18] I'm first going to comment out "initiateCheckout" for now. Instead, I'm going to run addToCart. I'm going to pass it in an argument with the object with ID. We can actually see what's going on inside of our cartState. I'm going to console log out our cart value.
[3:34] Now if we open up our app and look at the DevTools, we can see our cart object where it only includes a property of products with an empty object, which is expected because that's our default state.
[3:44] Now if we scroll down and we click the Buy Now button, we can open up this cart objects inside the products and we can see our product ID along with the ID and quantity properties right inside that object. Similarly, if we click the same thing with our Space Jelly T-shirt, we can look inside of that cartState, see the different products, and we see that we have two IDs.
[4:03] With the logic that we added to see if it was inside of the cart already or not, if we click it one more time, we can again look inside of that cartState, and we can see that our first product has one item and our second product has two.
[4:14] One thing you might notice is we're not actually storing the independent values of the total cost and the total quantity. Because we have all the data we need to calculate those total values, we can use our cartState as is to calculate both our total cost and our total quantity. This reduces the complexity and the moving pieces that we would have to manage inside of an instance of state.
[4:35] To start, I'm going to take the products that are inside of our cartState and turn it into an array of items. I'm going to create a new constant called cartItems. I'm going to set that equal to the objectKeys function where I'm going to use cart.products, which will leave me with an array of all of our products IDs that are currently in the cart.
[4:53] Next, I'm going to add a map function to that where we're going to use the key as our argument to that function, where we're first going to create a new constant called product. For that product, we're going to use the products array that we're storing inside of our JSON file.
[5:06] We're going to use the find method where we're going to look inside of that product object and find it's ID. We're going to say we want the item where our ID is equal to our key. Finally, we're going to return a new object where we're first going to spread out our cart products. Additionally, we're going to add a new property called pricePerItem. We're going to set that equal to product.price.
[5:30] In the end, our cartItems array is going to be equal to all the items that are inside of our cart, but we're additionally going to add the pricePerItem so that we know how much it costs. To test this out, let's console log out our cartItems so we can see what that looks like.
[5:45] Now, once we add a few items to our cart, we see that it's an array of two objects where we can still see our quantity, but we know also see our price for each one of those items. Now with all this information, we can calculate both our subtotal and our total quantity.
[5:59] Starting with subtotal, let's create a new constant called subtotal. For that, we're going to use cartItems and use the reduce method where we're going to pass in a new function. Because we're calculating a number, we want our initial value to be zero.
[6:13] Inside of our reduce function, we're going to use the first two arguments. The first one is accumulator. That's going to be our subtotal value where we continue to add the cost to it. Our second argument is going to be our current item, which we're going to destructure our pricePerItem and our quantity.
[6:30] With that, we can return our accumulator plus our pricePerItem times our quantity where for each time this loops through a new array item, we're going to take the current value, which is our accumulator, and we're going to add an additional value that's equal to the quantity times the pricePerItem.
[6:49] If we have one item at $10, it's going to be $10 that gets added. If we have two items at $10, it's going to be $20 that gets added. To test this out, let's console log out our subtotal so that we can see that value.
[7:02] We can see that because we set that initial value to zero, our subtotal is currently zero. Every time we add a new item to our cart, we can see that that's incrementing. Now that we have our subtotal value, we can go up to our total cost. Inside our code, we can replace this value with subtotal. We can see that it's immediately updated.
[7:20] Now let's do the same thing with our number of items. For our quantity, it's going to be very similar to our subtotal. So similar that we can actually first clone this block for our subtotal. We can rename subtotal to totalItems where instead of multiplying the pricePerItem times the quantity, we only want to know how many items are getting added.
[7:39] We're going to replace that with just simply the quantity, meaning we also don't need this pricePerItem on our second reduce function. Now let's take that totalItems value. Again, we can replace that static value with our new variable.
[7:51] When the page refreshes, we can also see that we have our updated items. To test this out, we can refresh the page. I can scroll down. I can add two of my combo pack and maybe one set of stickers where if we scroll up, that's equal to three items and $60.
[8:04] Finally, now that we have our cart, we need to wait for somebody to actually purchase those items in the cart. What we're going to do, any time somebody clicks the Checkout button, we're going to fire that Initiate Checkout button that we previously had on our products.
[8:17] On my button, I'm going to add a new onClick handler. I'm going to set that equal to a new function we're going to define called checkOut. Up top, I'm going to define my new function called checkOut.
[8:27] Inside of our product buttons, we still have that initiateCheckOut function. I'm going to first uncomment that out. I'm going to cut it out of this function. Then I'm going to paste it right inside of our checkOut function.
[8:37] If you noticed, this lineItems array is very similar to what our cartItems look like. The only difference, which is represented here, is that instead of a price, we have an ID that we need to pass in, which is the same ID that we use inside of our cartItems array.
[8:51] Instead of defining this array, we're going to pass in the value of cartItems. We're going to add the map method where we're going to pass in a new function with an argument of item, and we're going to return an object where our price property is going to be equal to item.ID, where our quantity is going to be equal to item.quantity.
[9:10] Finally, before we test this out, each of our product buttons still say Buy Now, where now we're just adding them to cart. Let's update these buttons to say Add To Cart. Back in our browser, we can add a few items back into our cart. If we scroll back up to the top, we can hit the Checkout button where we can see that we get taken to Stripe, and we have both of our items inside of our cart.
[9:30] In review, we had our Space Jelly shop but we only had the ability to purchase one product at a time. To fix that, we were able to add a shopping cart where we see the total number of items we have and the total cost with the ability to check out those items.
[9:43] Inside our code, we created a new instance of state called cart that we use to track our shopping cart, that we use to create our cartItems and calculate both our subtotal and our total number of items where any time we add a new item to our cart with our addCart function, we could initiate our checkout sequence using our dynamic cartItems value.
[10:01] So then any time I added some items to my cart that I wanted to buy, I had my dynamically updated cart where I can check out and get redirected right to Stripe.
This is why https://reactjs.org/docs/strict-mode.html
Hi, I tried using this <React.StrictMode> but no warning shows, can you specify please? I have checked and the issue is also present if downloading the lesson github code.
Try this build your next.js app and run yarn start
and see if u are getting the problem
You have to go to next.config.json and then you set reactStrictMode
to false
An issue with printing out the subtotal when clicking on "Buy Now"
console log prints "subtotal NaN"
const subtotal = cartItems.reduce( (accumulator, { pricePerItem, quantity }) => { return accumulator + pricePerItem * quantity; }, 0 );
Here is the link to the finished index
page on Colby's GitHub. Check your code against what Colby has and see if there is anything different.
My quantity doesn't increment one by one, it's weird but first time clicking the add to cart button it shows 1 quantity, but if clicking again it shows 3 quantity (instead of 2), and if clicking again it shows 5 quantity (instead of 3). Any idea?