开发者问题收集

无法在 React 应用程序中发现错误

2021-05-20
690

我一直在使用 React 编写应用程序(电子商务,作为项目,遵循教程)。引用购物车对象时,我收到错误“TypeError:无法读取未定义的属性‘length’”。以下是一些背景信息。我正在使用 App.js 组件顶部附近的 useState 钩子生成购物车对象:

const [cart, setCart] = useState({});

在 App.js 中再往下一点,console.log 语句执行时没有错误,表明购物车对象存在:

console.log(cart);

但是,当我尝试将购物车对象传递给 App.js 渲染部分中的购物车组件时,会生成上述错误(例如“TypeError:无法读取未定义的属性‘length’”)。为什么会发生这种情况?我该如何解决?

这是 App.js 的代码

import React, { useState, useEffect } from 'react'
import { commerce } from './lib/commerce';

import { Products, Navbar, Cart } from './components';

const App = () => {
    const [products, setProducts] = useState([]);
    const [cart, setCart] = useState({});

    const fetchProducts = async () => {
        const { data } = await commerce.products.list();
        setProducts(data);
    }

    const fetchCart = async () => {
        setCart(await commerce.cart.retrieve());
    }

    const handleAddToCart = async (productID, quantity) => {
        const item = await commerce.cart.add(productID, quantity);
        setCart(item.cart);
    }

    useEffect(() => {
        fetchProducts();
        fetchCart();
    }, []);

    
    console.log(cart);
    return (
        <div>
            <Navbar totalItems={cart.total_items} />
            {/* <Products products={products} onAddToCart={handleAddToCart} /> */}
            <Cart cart={cart} />
        </div>
    )
}

export default App

这是我将购物车对象传递到的组件(购物车)的代码:

import React from 'react'
import { Container, Typography, Button, Grid } from "@material-ui/core";
import useStyles from './styles';

const Cart = ({ cart }) => {
    const isEmpty = !cart.line_items.length;
    const classes = useStyles();

    const EmptyCart = () => {
        <Typography variant="subtitle1">
            You have no items your shopping cart..
        </Typography>
    }

    const FilledCart = () => {
        <>
            <Grid container spacing={3}>
                {
                    cart.line_items.map((item) => (
                        <Grid item xs={12} sm={4} key={item.id}>
                            <div>{item.name}</div>
                        </Grid>
                    ))
                }
            </Grid>
            <div className={classes.cardDetails}>
                <Typography variant="h4">
                    Subtotal: { cart.subtotal.formatted_with_symbol }
                </Typography>
                <div>
                    <Button className={classes.emptyButton} size="large" variant="contained" type="button" color="secondary">Empty Cart</Button>
                    <Button className={classes.checkoutButton} size="large" variant="contained" type="button" color="primary">Checkout</Button>
                </div>
            </div>
        </>
    }
    return (
        <Container>
            <div className={classes.toolbar} />
            <Typography className={classes.title} variant="h3">Your shopping cart</Typography>            
            {
                isEmpty ? <EmptyCart /> : <FilledCart />
            }
        </Container>
    )
}

export default Cart
2个回答

问题

您的代码存在问题,即初始状态与您在初始渲染时访问的状态不匹配。

  1. App 组件中, cart 状态 只是 一个空对象。

    const [cart, setCart] = useState({});
    
  2. cart 作为 prop 传递给 Cart 组件,并且代码假定 cart.line_items 已定义,以便访问 length 属性或 map 函数。 cart.line_items OFC 未定义,因此尝试访问 length 属性( map )会引发 TypeError:无法读取未定义的属性“XYZ”

    const isEmpty = !cart.line_items.length;
    

    cart.line_items.map(.....
    

but when I console.log it out in App.js, it actually does print out the necessary information.

console.log(cart); 位于组件的函数主体中,因此它错误地将 cart 状态记录为无意的副作用,它应该从 useEffect 钩子中记录,以便您看到每个渲染周期的值。这里的另一个问题是您没有访问任何嵌套属性,因此这 永远不会 引发错误。我愿意打赌,使用 代码,您至少有 1 或 2 个日志条目只是空对象 ( { ) 然后 您会看到一些带有填充嵌套的日志属性。

Example possible log output:

{}
{}
{ line_items: [.....], subtotal: ..... }
{ line_items: [.....], subtotal: ..... }

解决方案

关于状态日志记录,您应该使用 useEffect 钩子,该钩子依赖于您正在记录的状态值。这将在初始渲染时记录 cart 状态,并且稍后仅在 cart 状态值更新时记录。

useEffect(() => {
  console.log(cart);
}, [cart]);

对于错误,您有几种选项可以帮助防止在访问 cart 状态时出现错误。

  1. 提供与渲染周期内访问的内容相匹配的有效初始状态,将 line_items: [] 添加到初始 cart 状态,这样 cart.line_items 现在将存在并具有 length 属性。

    const [cart, setCart] = useState({ line_items: [] });
    
  2. 在传递的 cart 属性上使用保护子句或可选链接。

    const isEmpty = !(cart.line_items && cart.line_items.length);
    

    const isEmpty = !cart.line_items?.length);
    

    cart.line_items && cart.line_items.map(.....
    

    cart.line_items?.map(.....
    

    如果 cart.subtotal 未定义,也可以保护 subtotal 子状态。

    <Typography variant="h4">
    Subtotal: { car​​t.subtotal?.formatted_with_symbol }
    </Typography>
    
Drew Reese
2021-05-20

还有另一种方法可以解决错误(它解决了我的问题)。

我们只需要添加此行:

if (!cart.line_items) return "Loading...";

然后从顶部删除变量并将其添加到 if 语句中:

{!cart.line_items.length ? <EmptyCart /> : <FilledCart />}

因为有时如果内容本身足够有意义,我们不需要创建变量。

代码:

import React, { useEffect } from "react";
import { Container, Typography, Button, Grid } from "@material-ui/core";

import useStyles from "./styles";
//
//
const Cart = ({ cart }) => {
 
  //   const isEmpty = !(cart.line_items && cart.line_items.length);


  const classes = useStyles();
  //
  //So if the cart is EMPTY show the following:
  const EmptyCart = () => {
    <Typography variant="subtitle1">
      You have no items in your shopping cart, start adding some!
    </Typography>;
  };
  //
  //
  //So if the cart is FILLED show the following:
  const FilledCart = () => (
    <>
      <Grid container spacing={3}>
        {cart.line_items.map((item) => (
          <Grid item xs={12} sm={4} key={item.id}>
            <div>{item.name}</div>
          </Grid>
        ))}
      </Grid>
      <div className={classes.cardDetails}>
        <Typography variant="h4">
          Subtotal: {cart.subtotal?.formatted_with_symbol}
        </Typography>
        <div>
          <Button
            className={classes.emptyButton}
            size="large"
            type="button"
            variant="contained"
            color="secondary"
          >
            Empty cart
          </Button>
          <Button
            className={classes.checkoutButton}
            size="large"
            type="button"
            variant="contained"
            color="primary"
          >
            Checkout
          </Button>
        </div>
      </div>
    </>
  );

  //
  if (!cart.line_items) return "Loading";

  return (
    <Container>
      <div className={classes.toolbar} />
      <Typography className={classes.title} variant="h3">
        Your shopping Cart
      </Typography>
      {!cart.line_items.length ? <EmptyCart /> : <FilledCart />}

    </Container>
  );
};

export default Cart;
Sequoie
2021-10-16