【问题标题】:React Redux - Cannot set property 'quantity' of undefinedReact Redux - 无法设置未定义的属性“数量”
【发布时间】: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 操作而不引发错误。


这是我迄今为止尝试过的:

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 中取出。

我今天阅读了许多其他关于此主题的 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 是通过 &lt;Link&gt;Home.js to Item.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


【解决方案1】:

您的问题是您在传递给Item 组件时改变了item.id 的数据类型。您将所选项目作为字符串放入位置历史记录中。但是然后在 reducer 中对整数执行 === 到字符串,这将不起作用。 (又名1 === '1')。

<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>

有几种方法可以解决此问题。
1.Pull the item from your actual state based on the id in the URL.
2. 调度一个活动项目操作,用您正在查看的活动项目更新您的减速器。

我认为第一个选项非常简单,对代码的更改更少,这就是它的样子。

// Home
<Link
  to={{
    pathname: `/products/${item.category}`,
    search: `?id=${item.id}`
  }}
  component={Item}
>
  <img src={item.img} alt={item.title} />
</Link> 

然后在项目中,

// Item
constructor(props) {
  super(props);
  let currentItemId = new URLSearchParams(window.location.search).get("id");
  if (currentItemId) {
    currentItemId = parseInt(currentItemId, 10);
  }
  this.state = {
    item: props.items.find(item => item.id === currentItemId)
  };
}

【讨论】:

  • 感谢您的帮助——您的解决方案和详尽的解释非常有帮助。现在看起来很明显,我觉得我有一些基础 JS 的复习要做……我将在我当前的代码中实现这个策略,并确保在此过程中查阅 MDN 文档。再次感谢您为此付出的时间和耐心!
  • @Jenna 乐于助人!如果您需要更多关于我如何计算出来的帮助,我可以提供调试技巧和东西:)
  • @Jenna 所以,首先要找到问题出在TypeError: Cannot set property 'quantity' of undefined。这意味着您实际上是在调用undefined.quantity = something。找到您设置数量的位置后,我在您创建 addedItem 并打开开发工具以查看其是否未定义后立即在减速器中添加了 debugger 行。在确认其未定义后,我查看了您用于查找item 的条件。然后查看action.id 看看它是什么,这就是我看到'1' 的地方。所有这一切都是在触发调试器断点时发生的,因此您可以检查实际值。
  • 在弄清楚它是一个字符串 id 之后,我查看了 action 函数,但不是直接通过,所以我去了组件 Item.js 看看它是如何调用的。由于那里没有它的突变,但在构造函数中看到 (item: this.props.location.state),我寻找它在位置状态中设置的位置。在Home.js 中找到它。 :) 请记住始终从问题所在开始,调试它以查看实际无法正常工作的内容。然后在逻辑上顺着树向上找出错误在哪里!希望有帮助! :)
  • 这些技巧确实有帮助。我没有太多使用 DevTools(只是在控制台上记录了一些东西和一点移动开发,但没有太疯狂)。我将对其进行更多探索——尤其是在调试方面!
【解决方案2】:

我确实感觉这就是您的问题所在,您正在将 boolean 值传递给 id 道具。它应该是ownProps.idstate.id

const mapStateToProps = (state, ownProps) => {
    return {
        items: state.items,
        id: ownProps.id === state.id
    };
}

【讨论】:

  • 我已将 mapStateToProps() 更改为 not 包含布尔值,但我仍然收到相同的错误...谢谢您的回复!
猜你喜欢
  • 2020-10-10
  • 2019-02-15
  • 1970-01-01
  • 2016-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-06-22
  • 2021-01-15
  • 2017-09-05
相关资源
最近更新 更多