无法在 React 应用程序中发现错误
我一直在使用 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
问题
您的代码存在问题,即初始状态与您在初始渲染时访问的状态不匹配。
-
在
App
组件中,cart
状态 只是 一个空对象。const [cart, setCart] = useState({});
-
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
状态时出现错误。
-
提供与渲染周期内访问的内容相匹配的有效初始状态,将
line_items: []
添加到初始cart
状态,这样cart.line_items
现在将存在并具有length
属性。const [cart, setCart] = useState({ line_items: [] });
-
在传递的
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>
还有另一种方法可以解决错误(它解决了我的问题)。
我们只需要添加此行:
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;