【发布时间】:2020-01-10 18:07:10
【问题描述】:
我正在使用 React、Redux 和 React-Router 构建一个电子商务网站。到目前为止,我有一个包含所有商品的主页和一个将商品添加到购物车的链接。每个商品的商品详情页都有<Link>s,并且这个商品详情页还有一个“加入购物车”按钮。
我需要addToCart 操作来获取正在查看的商品的 ID,并将其添加到购物车。我的问题是,在项目详细信息页面上,当单击“添加到购物车”按钮时,我收到 cartReducer.js 文件的此 TypeError:TypeError: Cannot set property 'quantity' of undefined
这只发生在商品详情页面,而不是主页。我感觉这与所有项目都是从主页上的map() 函数呈现的事实有关...?
在项目详细信息页面上,项目基于从主页的<Link> 传递的道具(location.state)呈现。我不知道如何将项目 ID 从 URL 传递到 addToCart 操作而不引发错误。
这是我迄今为止尝试过的:
- 在
mapStateToProps中使用ownProps(此处建议How to sync Redux state and url query params 和此处What is ownProps in react-redux?)下面的代码是我离开的地方,但我尝试过以不同方式使用ownProps:
Item.js
const mapStateToProps = (state, ownProps) => {
return {
items: state.items,
id: ownProps.id === state.id
// I've also tried id: ownProps.id
};
}
我意识到尝试上述方法对我不起作用,因此我将ownProps 完全从Item.js 中的Item.js 中取出。
- 确保调度的动作/事件处理程序被正确绑定(我参考了这篇文章:https://www.freecodecamp.org/news/the-best-way-to-bind-event-handlers-in-react-282db2cf1530/)。
我今天阅读了许多其他关于此主题的 SO 问题和答案以及信息性文章,但我忘记了我所引用的所有内容。
我只是不明白为什么项目 ID 不能从 Item.js 传递给减速器,但它对 Home.js 非常有效
我已经把代码放在codesandbox这里:
https://codesandbox.io/s/github/thesemdrnsocks/redux-co
到目前为止,这是我的代码:
Item.js(呈现商品详情页面 - 无法从此组件将商品添加到购物车)
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(呈现主页,其中显示商店中的所有商品——从此处将商品添加到购物车)
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(摘录)
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(摘录;保存商品信息)
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...
【问题讨论】:
-
您的代码中有错误。如果您的
find搜索没有返回值,则减速器中的addedItem可以是未定义的。这会发生在您的项目数组中不存在的东西 -
我会记住这一点,进行一些更改,稍后再报告。感谢您的洞察力!
-
您对
find函数的评论是有道理的。对我来说没有意义的是,项目 ID 是通过<Link>从Home.jstoItem.js传递的,但在使用时它仍然是减速器中的undefined来自Item.js... 看起来reducer 可以访问来自Home.js的ID,但是Item.js无法将正确的信息传递给reducer,即使Item.js似乎可以访问与@987654355 相同的信息@ 零件。我已经编辑了我的问题以包括Home.js。再次感谢您的洞察力。 -
你能在代码沙盒上复制这个问题吗?很难通过猜测来调试问题:) 确实看起来不对的一件事是
id: ownProps.id === state.id这是一个布尔值而不是一个 ID。另外,你为什么在家里和物品上写addToCart? home 不应该只渲染项目并让项目添加到购物车吗? -
我已经更改了
ownProps.id === state.id,但它似乎没有帮助。我已经更改了Home.js配置,当用户点击时可以在Item.js页面上看到该信息时,添加到购物车和描述看起来有点混乱。这是代码框的链接:codesandbox.io/s/github/thesemdrnsocks/redux-co
标签: reactjs react-redux react-router