I’m building an e-commerce website using React, Redux, and React-Router. So far, I have a home page with all items and a link to add the items to the cart. There are <Link>
s to an item details page for each item, and this item details page also has an “add to cart” button.
I need the addToCart
action to take in the ID of the item being viewed, and add it to the cart. My problem is that on the item details page, when the “add to cart” button is clicked, I receive this TypeError for the cartReducer.js
file: TypeError: Cannot set property 'quantity' of undefined
This only happens from the item details page, but not from the home page. I have a feeling this has to do with the fact that all of the items are rendered from a map()
function on the home page...?
On the item details page, the item is rendered based on props (location.state
) which are passed from the <Link>
from the home page. I can’t figure out how to pass the item ID from the URL to the addToCart
action without it throwing errors.
This is what I’ve tried so far:
- Using
ownProps
inmapStateToProps
(as suggested here How to sync Redux state and url query params and here What is ownProps in react-redux?) The following code is where I left off with this, but I have tried usingownProps
in different ways:
Item.js
const mapStateToProps = (state, ownProps) => {
return {
items: state.items,
id: ownProps.id === state.id
// I've also tried id: ownProps.id
};
}
I realize that trying the above didn't work for me, so I've taken ownProps
out of the mapStateToProps()
in Item.js
completely.
- Assuring that the dispatched action/event handler is bound correctly (I referenced this article: https://www.freecodecamp.org/news/the-best-way-to-bind-event-handlers-in-react-282db2cf1530/).
I’ve read many other SO questions & answers and informational articles on this topic today, but I’ve lost track of all that I have referenced.
I just don't understand why the item ID can't be passed to the reducer from Item.js, yet it works perfectly well for Home.js
I have put the code on codesandbox here:
https://codesandbox.io/s/github/thesemdrnsocks/redux-co
Here is my code so far:
Item.js (renders the item details page--adding an item to the cart from this component does not work)
import React from 'react';
import { connect } from 'react-redux';
import {
Link,
withRouter
} from 'react-router-dom';
import { addToCart } from '../../actions/CartActions';
const mapStateToProps = (state, ownProps) => {
return {
items: state.items,
id: ownProps.id === state.id
};
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => { dispatch(addToCart(id)) }
};
}
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
item: this.props.location.state,
}
}
handleAddToCart = (id) => {
this.props.addToCart(id);
}
render() {
const item = this.state.item;
return (
<div className="item-details" key={item.id}>
<div className="item-details-img">
<img src={item.img} alt={item.title} />
</div>
<div className="item-details-info">
<p className="item-details-title">{item.title}</p>
<p className="item-details-price"><b>${item.price}</b></p>
<p className="item-details-desc">{item.desc}</p>
<div className="item-details-add-to-cart">
<Link to="/cart" onClick={ () => { this.handleAddToCart(item.id) } }>
<button>Add to Bag</button>
</Link>
</div>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Item));
Home.js (renders the home page, which displays all items in the store--adding an item to the cart works from here)
import React from 'react';
import { connect } from 'react-redux';
import {
Link,
withRouter
} from 'react-router-dom';
import { addToCart } from '../actions/CartActions';
import Item from './shopping/Item';
const mapStateToProps = (state) => {
return {
items: state.items
};
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => { dispatch(addToCart(id)) }
};
}
class Home extends React.Component {
handleAddToCart = (id) => {
this.props.addToCart(id);
}
render() {
let itemList = this.props.items.map(item =>{
return (
<div className="home-item-card" key={item.id}>
<div className="home-item-image">
<Link to =
{{ pathname: `/products/${item.category}`,
search: `?id=${item.id}`,
state: {
id: `${item.id}`,
img: `${item.img}`,
title: `${item.title}`,
desc: `${item.desc}`,
price: `${item.price}`
} }}
component={ Item }>
<img src={item.img} alt={item.title} />
</Link>
</div>
<div className="home-item-info">
<span className="home-item-title">{item.title}</span>
<Link to="/" className="home-add-item" onClick={() =>
{ this.handleAddToCart(item.id) } }>
<i class="fa fa-plus-circle"></i>
</Link>
<p className="home-item-price"><b>${item.price}</b></p>
<p className="home-item-desc">{item.desc}</p>
</div>
</div>
)
})
return (
<div className="home-new-arrivals">
<h1>What's new?</h1>
<div className="new-arrivals-items">
{itemList}
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Home));
cartReducer.js (an excerpt)
const initState = {
items: ShopContent,
addedItems: [],
total: 0
}
const cartReducer = (state = initState, action) => {
// If item is added to cart...
if (action.type === ADD_TO_CART) {
let addedItem = state.items.find(item => item.id === action.id);
let existedItem = state.addedItems.find(item => action.id === item.id);
// Check if item already exists in cart
// If item is already in cart, increase quantity by 1, and calculate total
if (existedItem) {
addedItem.quantity += 1;
return {
...state,
total: state.total + addedItem.price
}
} else {
// Add item to cart and calculate total
addedItem.quantity = 1;
let newTotal = state.total + addedItem.price;
return {
...state,
addedItems: [...state.addedItems, addedItem],
total: newTotal
}
}
}
/* ... the rest of the reducer covers changing quantity of items
in cart and adding/removing shipping from the cart total */
ShopContent.js (an excerpt; holds item information)
import Item1 from '../images/item1.png';
// ... followed by more imports for each item's image
export const ShopContent = [
{
id: 1,
title: 'Black Eyelet Dress',
desc: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
price: 120,
img: Item1,
category: 'dresses',
sale: false
},
// ... followed by more items...