For the add to cart feature, we'll need to work on two things -
one is of course that we can have a cart screen, that we have an
icon here in our header that takes us to the cart screen where we can see like the total sum and the
cart items and where we have that order now button
but I'd say that's the second step because the more important first step is that we add the logic
to manage cart items and that involves Redux. For this,
I'll add a brand new reducer, a second reducer which I'll name cart, cart.js and also an actions file
and this is now actually an action file we will populate.
We'll do that with the products file later too
once we dive into adding products and so on but for now, let's start with the cart feature. So here in
the cart.js file in the reducers folder,
I'll set up my initial state for that cart slice and again, it's all these separate reducers and the
states they manage to make up that total Redux store because in the end, that's what you then combine
here with combined reduces into one root reducer and one big state in the end.
We just have these individual sub-states to make managing the data easier.
So here in the cart reducer, how should the state for our cart look like?
Well our cart will have a bunch of items, could be an array and a sum which initially is zero, so a total
amount you could say, you could of course also name this total amount maybe to make it really clear what
this is.
So these are the two things that make up my cart here
and as always, I can't emphasize this enough, there is more than one way of implementing this. So if you want
to manage some other data here as well,
definitely do so,
there is no single right or wrong approach,
this is just my approach of building this app.
Now it's important that for this cart here, I want to make sure that if we have an item in the cart already,
let's say the red shirt and I click to cart a second time,
I don't add two items to this array but I still only have one item in there and instead every item in
that items array should be an object where I manage like the ID of the product maybe, where I have
the title and the quantity of the item in the cart,
so that would then change to 2 if I click on it a second time.
Therefore you could actually also argue that items maybe shouldn't be an array but maybe a Javascript object
because that allows us to store the ID of the product as a key and the value could be an object with
the title, with the quantity, with the price and
that's just an alternative,
you could also do this all with an array but that's actually the approach I will use.
Now the reducer I export therefore is a reducer which of course gets a state which has our initial state
as a default value and an action as an argument which in the end needs to return some data.
So in the end, it needs to return a new state which is then used by Redux.
Now we need an action to continue.
So here I'll export a constant, an action identifier and I'll name this add to cart, the identifier is
up to you, I'll go with add to cart like this and in the actions file, I will create such an action
creator function.
So here another constant which I'll name
add to cart which receives let's say the full product object which I want to add, so that I can pull
out the information I need and then in here, we have to return such an action object which has a type which
is add to cart and then our product.
This is just a way of creating these actions that makes it very simple for us to do that inside of our
components.
So in the end, in the cart reducer, in all reducers actually but in the cart reducers place where we care,
we'll get that
add to cart action, so here we need to switch the action type and handle the case that it's add to cart
and therefore you should import
add to cart from that actions folder and there, from the cart file. Now and by the way important,
this action when it's dispatched is received on all reducers but if you don't handle it with a separate
case, your default case will kick in which normally should just return your current state slice for this
reducer, which means the state slice for this reducer is unchanged. Here
however in the cart reducer, I of course want to change my state,
hence I do handle to add to cart
case here and now in add to cart,
what should happen there?
Well we get our added product, we can pull that out of the action because there in the action, we'll have
a product key,
that's what we just set up here in the action cart.js file,
so then I'll get our product and I'm storing it in a constant, added product here with which we can work
now
and we'll have our product price here which is of course addedProduct.price and we'll have our product
title which is addedProduct.title. Before we continue, let's define how cart item,
so what we store here should look like, the value we have in there.
So I'll go to my models folder and add a cart-item.js file here and then add a cart item class
which receives a constructor
and before I forget it again, which needs to be exported here and now in that constructor, I expect
to get the quantity of the product we're about to add, the product price,
the product title and the sum for this item, if we have three times the red shirt, our sum would be three
times 29.99,
so roughly around $90.
Now what I don't get here is the product ID because I will use that as a key inside of my object here
which items holds and then will become clearer once we implement it.
So here in the cart item, I just store all the data I receive,
quantity, product price and I have that data
in there which I need to output later, so product title and the sum, like this
and now back in the reducer, we can of course import this cart item class from the models folder and from
cart item here and now back in that add to cart case down there,
we first of all need to find out whether that product is already part of our items.
Now as I mentioned, I'll add every new product by adding a new key to items which is the ID of that
product, since that is unique
we always have each key only once and then the value will be a cart item as we just defined it in models
and hence I know if the ID of the product I'm getting here is already a key of items, then this already
has our item as a cart item.
So here, we can simply check if items for addedProduct.ID exists,
so if that returns something true-ish, so not undefined, then we already have the item in the cart which
is okay but then we just need to change the quantity,
otherwise we'll need to add a brand new item.
So let's start with the else case because that's the case we'll first encounter when we add something
for the first time,
there we need a new cart item, I create that with the new keyword
and the cart item class I defined and in there, we need to provide the quantity which of course is one
if we just add a new item,
we need to provide the product price, the product title here and the sum and the sum of course initially
is just our product price, right?
Because if we add one item of that price, then the sum for this cart item is our product price
and now this new cart item has to be added to our cart items here.
So here, we return a copy of our state
and we set items equal to a new object where I copy all my existing state items in, so that I copy my
existing items
object here and then I add a new key with this dynamic syntax with the square brackets where the key
name is addedProduct.ID, that's what I meant with the ID of the product being the key in our
items object and the value is our new cart item.
This is just a syntax you have to know, it's vanilla Javascript, this is how you can add or access as up
here a dynamic property instead of hardcoding it as a string here. By the way, this up here should of course
also be state.items, my mistake. So with this, we should add a new cart item here.
Now if you already have an item in the cart, then of course we don't create a new cart item, instead we
want to update the existing one. So I'll have my updated cart item here and for that, I still create a
new cart item but I'll prepopulate it with some existing data,
for example the quantity here. We get that from state items for the ID of the added product because
that should already be part of the state items, we're checking this here and this will have a quantity
key of course because such an item here in my items is just of type cart items, so it'll have a quantity
property and
we simply add one to that because we're adding a new cart item,
so we have to increment this. Now cart item as a second argument takes the product price. Then of course
since we will build the app such that the price never changes which would add a lot of complexity here,
we would have to manage the cart in a totally different way in that case, we would have to store each cart
item as a list of transactions for this cart item which is too much for this here.
So since this never changes, since the price never changes, we can in the end just take the prod price we're
getting or take the existing price we stored before because that again will never change.
The title might change and I want to store the most recent title here, so I'll definitely take the one
of the product we're getting here instead of my old snapshot
and last but not least, the last value here, my total sum for this cart item,
that of course is the current sum, so I access state items for the ID here, sum plus the product
price because we add one new item here, we have to add the price times 1 to the current sum. That's the
updated cart item.
Now with that, we can return our new state slice here by copying the old states and setting items equal
to and now important, to an object where we copy state.items but where we now again set addedProduct.ID
and yes, this exists already but with that we simply override it with our updated cart item,
so we do the same update we do down there. Now
that however is not all we need to do,
we need to change the total amount as well because that of course changes.
So when we add an item for the first time, then the total amount should be basically our old total amount,
so state total amount plus product price and it's the same
if we add an item to an existing cart or when it already exists in there because in the end, our total
always just is the old total plus the price, because we always add one item of that product,
that's how this app will work.
So our update statement here is almost the same,
the only thing that differs is here how we create that cart item, therefore we can of course also
reuse this code, move this out of the if else block and instead of having two different constants here,
we can create a variable before we enter the if statement,
updated or a new cart item
maybe, that could be a name because we don't know what it will be in advance
and then here in the if branch here, we set updated or new cart item to this updated cart item, here
on the other hand we set it to this new cart item and here, we then always use the updated or new cart
item and with that, we have some code reusage here and
don't repeat ourselves which is always good. One other side note, copying the state here of course
is always redundant because you always set both items and total amount.
So if you always only have these two fields in here and you always change the both, you don't need to
copy the existing state, if you ever introduce another piece of data to your state slice here which you
don't change down here however, you need to copy your old state so that you don't lose that additional
data.
Hence here I will leave that copying functionality even though we don't technically need it here but
if we ever should change our state where saved and we don't forget to copy it
and we therefore lose data.
Well, there was a lot of work, with that
we can start dispatching this action
and of course, I want to dispatch it from the product overview and the product detail
because in both screens, I have add to cart buttons.
So let's start on the products overview screen, there to dispatch Redux actions we need to include the use
dispatch hook here,
with that here we can simply get access to this dispatch function by calling use dispatch like this
and now when we click on add to cart down there, we can dispatch our action here
and of course by the way instead of using inline functions here, you could have functions stored in separate
constants inside of your functional component here,
as an alternative, I'll stick to inline functions for now and I will import all my actions here as cart
actions from the store folder and there, the actions folder, the cart file.
Now this is an import syntax which merges all exports from the file together into one object,
we only have one export here
therefore you could also target that specific export,
later we'll have multiple exports and therefore I'll use this approach.
So here, we then dispatch cart action.
add to cart and this is a function we have to execute, this is our action creator function and this as
an argument takes our product,
so in the end here, we have to pass in itemData.item which is just our product here in the product
detail screen.
Therefore we do the exact same thing, we import use dispatch from React Redux,
therefore here we can get access to the dispatch function by calling use dispatch
and then for this button here in the inline function or in a separate named function if you like that,
you can call dispatch,
now we need to import our actions and I'll use the same syntax as before where I import everything as
cart actions from the store folder, the actions folder, the cart file and now cart actions, call add to
cart here and forward the selected product which is our product we want to add to the cart here.
This hopefully dispatches actions such that the item is added to the cart but of course right now we
can't confirm this.
Well actually we can. You can spin up React Native debugger and I haven't used this before but in there, you
can now hit command or control t to open a new tab, then confirm this import here,
and then
go to your apps
and in there, open the debugging menu on iOS with command D, on Android with control or command
M and
click on debug remote Javascript. Now mine opened on Port 19003 actually, so I opened the React Native
debugger on the wrong port,
select me again hit command T or control T in the debugger and choose 19003, use whatever port open
for you in Chrome, click confirm and now this connects to your running React app here in the debugger,
make sure you close your Chrome debugger and then reload your app,
now this should connect here and now not only can you inspect your app here
as I explained it earlier,
no,
you can also have a look at the part up there and that is your Redux debugger,
there you can see what Redux is doing for you.
The only thing you have to do for this to make it work is install a new package with npm install
--save dev because it's a development dependency which we only need during development and there,
install the Redux dev tools extension,
go to your app.js file thereafter and then import
from this Redux
dev tools extension. You can import compose with dev tools from there and
that's a function you pass to create store as a second argument,
you can simply execute it there like this. Now by the way,
this is code, this
import and this usage you should remove before you deploy your app because this is only something that
should be running during development, not in your production app, you need to remove that before you
deploy your app but during development, that is fine. And with that added, if you now save this, you'll actually see
that here in this Redux Inspector, you see an init action being dispatched, you didn't see that before.
That's an action which automatically is dispatched when your app starts.
If I now click on to cart here, you'll also see the add to cart action and
now here, you can view details about that action like the data that was attached to it and about your
state here and there you see for example this is my product state and
where is my cart state?
Well that's of course missing because whilst we have the reducer in the action set up, in the app.js
file, in combined reducers, we're not including the cart state.
So there, let's import the cart reducer from ./StoreReducers/cart and now with that imported,
we can add it to our combined reducers object and now there, we add a cart slice for example, point at
the cart reducer and therefore now this gets included into our Redux store. With that if you now wait for
this to reload and you click on to cart again, now in this add to cart action, under state,
you'll see that now there is a cart slice and now you see there is a total amount, you see there is an
item with the key P1 which is our product with a quantity of 1, that price, the
sum of this and if I now click on to cart a second time here, there is another action dispatched and if
we have a look at the state after this action, we now see, cool
only one item but now with quantity two, product price per item is this
but the sum is this.
This is the shirt,
if I now dispatch the blue carpet as well,
we got to add to cart one more time and there in the state, we now indeed see there are two items
in there, P1 and P2,
P1 is unchanged but P2 was now added and the total amount also looks good to me.
So this seems to work and this is how you can look into your Redux state whilst your app is running without
outputting it here.
Now of course, we also want to output it visually but this is a nice way of looking into your state
behind the scenes with the help of the React Native debugger.
Now we'll close it here because it slows down the app a bit and therefore here in the debug menu, I'll also
stop my remote debugging and close the inspector here entirely.
Also back in the app.js file, I will get rid of the compose with dev tools thing here so
that I don't forget it later
but you can always add this here or leave it here whilst you are developing so that you can look into
your store.
The main takeaway for us here is that this seems to work
and with that working, of course we can now work on adding an action button here to the header and make
sure we can go to the cart page from that or with the help of that action button so that on the cart
page, we can output our cart information.