UseEffect 在 React 中运行无限渲染(超出最大更新深度)
UseEffect runs infinite renders in React (Maximum update depth exceeded)
我有一个购物车组件,里面有一组卡片。
每次用户从购物车中删除产品时,我都会将其从本地存储中删除,
并将其从 UI.
中删除
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Layout from '../Layout';
import Card from '../Home/Card';
import { getCart } from './cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(getCart());
}, [items]);
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id}
....... />
))}
</div>
);
};
const noItemsMsg = () => (
<h2>
Your cart is empty! <br /> <Link to='/shop'>Continue Shopping</Link>
</h2>
);
return (
<Layout
title='Shopping Cart'
desc='Manage your cart items'
className='container-fluid'
>
<div className='row'>
<div className='col-6'>
{items && items.length > 0 ? showItems(items) : noItemsMsg()}
</div>
<div className='col-6'>
<p>
Show Checkout Options / Shipping Address / Total / Update Quantity
</p>
</div>
</div>
</Layout>
);
};
export default Cart;
和Card.js:
import React, { useState } from 'react';
import moment from 'moment';
import { Link, Redirect } from 'react-router-dom';
import ShowImage from '../ShowImage';
import { addItemToCart, updateItem, removeItem } from '../Cart/cartHelpers';
const Card = ({product,
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => removeItem(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};
return (
<div className={!showProductFullSize ? 'col-4 mb-3' : ''}>
<div className='card'>
<div className='card-header name'>{product.name}</div>
<div className='card-body'>
.............. // more code
{showRemoveButton()}
.............. // more code
</div>
</div>
</div>
);
};
export default Card;
每当用户点击删除项目按钮时,我得到:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
解决此问题的最佳方法是什么?
因为这段代码(数组中的items
):
useEffect(() => {
setItems(getCart());
}, [items]);
我猜你会在渲染组件时收到“超出最大更新深度”警告,无需等待单击“删除”按钮。
原因是:items改变时会调用setItems,然后setItems会改变items,所以会造成无限递归。
useEffect(() => {
setItems(getCart());// setItems will change the items
}, [items]);
嗯,我认为当前的方法不是干净的方法,因为很难通过这种方式控制组件 re-rendering。
我的建议是去掉 useEffect 的使用,然后添加 onRemoveItems 作为 CardComponent
的 props
import { addItemToCart, updateItem, removeItem } from '../Cart/cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
//remove this
//useEffect(() => {
// setItems(getCart());
//}, [items]);
const removeItem = (id) => {
removeItem(id);
setItems(getCart());
}
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id} onRemoveItem={removeItem}
....... />
))}
</div>
);
};
const Card = ({product,
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => props.onRemoveItem(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};
return (
<div className={!showProductFullSize ? 'col-4 mb-3' : ''}>
<div className='card'>
<div className='card-header name'>{product.name}</div>
<div className='card-body'>
.............. // more code
{showRemoveButton()}
.............. // more code
</div>
</div>
</div>
);
};
export default Card;
问题的根本原因在于使用“useEffect”来更新状态。
useEffect只能用于执行side-effects。
副作用是 activity 由于状态改变而触发的。
side-effects的一些例子是:
- 在用户提供输入数据后提交表单。
- 加载页面时从 API 中获取 JSON 数据。
更新useEffect中的状态会导致无限循环。
注意到的问题是在 useEffect 中更新状态的场景示例。
每当调用 setItems 时,'items' 的状态就会得到
改变了。
状态改变会触发useEffect,目的是运行
side-effects.
useEffect 再次调用 setItems,这导致了无限循环。
如何解决问题?
问题可以通过删除 useEffect 挂钩来解决,因为在给定的场景中没有必要。
在 Cart.js 文件中:从 useEffect 中删除项目依赖项。在此处导入 removeItem
import { removeItem } from '../Cart/cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(getCart());
}, []);
// Add this function and pass it to Card component
const handleRemove = (id) => {
removeItem(id)
setItems(getCart())
}
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id} handleRemove={handleRemove}
....... />
))}
</div>
);
};
Card.js - 接收 handleRemove 作为 props
const Card = ({product, handleRemove
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => handleRemove(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};...
我有一个购物车组件,里面有一组卡片。
每次用户从购物车中删除产品时,我都会将其从本地存储中删除, 并将其从 UI.
中删除import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Layout from '../Layout';
import Card from '../Home/Card';
import { getCart } from './cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(getCart());
}, [items]);
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id}
....... />
))}
</div>
);
};
const noItemsMsg = () => (
<h2>
Your cart is empty! <br /> <Link to='/shop'>Continue Shopping</Link>
</h2>
);
return (
<Layout
title='Shopping Cart'
desc='Manage your cart items'
className='container-fluid'
>
<div className='row'>
<div className='col-6'>
{items && items.length > 0 ? showItems(items) : noItemsMsg()}
</div>
<div className='col-6'>
<p>
Show Checkout Options / Shipping Address / Total / Update Quantity
</p>
</div>
</div>
</Layout>
);
};
export default Cart;
和Card.js:
import React, { useState } from 'react';
import moment from 'moment';
import { Link, Redirect } from 'react-router-dom';
import ShowImage from '../ShowImage';
import { addItemToCart, updateItem, removeItem } from '../Cart/cartHelpers';
const Card = ({product,
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => removeItem(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};
return (
<div className={!showProductFullSize ? 'col-4 mb-3' : ''}>
<div className='card'>
<div className='card-header name'>{product.name}</div>
<div className='card-body'>
.............. // more code
{showRemoveButton()}
.............. // more code
</div>
</div>
</div>
);
};
export default Card;
每当用户点击删除项目按钮时,我得到:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
解决此问题的最佳方法是什么?
因为这段代码(数组中的items
):
useEffect(() => {
setItems(getCart());
}, [items]);
我猜你会在渲染组件时收到“超出最大更新深度”警告,无需等待单击“删除”按钮。 原因是:items改变时会调用setItems,然后setItems会改变items,所以会造成无限递归。
useEffect(() => {
setItems(getCart());// setItems will change the items
}, [items]);
嗯,我认为当前的方法不是干净的方法,因为很难通过这种方式控制组件 re-rendering。 我的建议是去掉 useEffect 的使用,然后添加 onRemoveItems 作为 CardComponent
的 propsimport { addItemToCart, updateItem, removeItem } from '../Cart/cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
//remove this
//useEffect(() => {
// setItems(getCart());
//}, [items]);
const removeItem = (id) => {
removeItem(id);
setItems(getCart());
}
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id} onRemoveItem={removeItem}
....... />
))}
</div>
);
};
const Card = ({product,
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => props.onRemoveItem(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};
return (
<div className={!showProductFullSize ? 'col-4 mb-3' : ''}>
<div className='card'>
<div className='card-header name'>{product.name}</div>
<div className='card-body'>
.............. // more code
{showRemoveButton()}
.............. // more code
</div>
</div>
</div>
);
};
export default Card;
问题的根本原因在于使用“useEffect”来更新状态。
useEffect只能用于执行side-effects。
副作用是 activity 由于状态改变而触发的。
side-effects的一些例子是:
- 在用户提供输入数据后提交表单。
- 加载页面时从 API 中获取 JSON 数据。
更新useEffect中的状态会导致无限循环。
注意到的问题是在 useEffect 中更新状态的场景示例。
每当调用 setItems 时,'items' 的状态就会得到 改变了。
状态改变会触发useEffect,目的是运行 side-effects.
useEffect 再次调用 setItems,这导致了无限循环。
如何解决问题?
问题可以通过删除 useEffect 挂钩来解决,因为在给定的场景中没有必要。
在 Cart.js 文件中:从 useEffect 中删除项目依赖项。在此处导入 removeItem
import { removeItem } from '../Cart/cartHelpers';
const Cart = () => {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(getCart());
}, []);
// Add this function and pass it to Card component
const handleRemove = (id) => {
removeItem(id)
setItems(getCart())
}
const showItems = () => {
return (
<div>
<h2>You have {`${items.length}`} items in your cart</h2>
<hr />
{items.map((prod, index) => (
<Card key={prod._id} handleRemove={handleRemove}
....... />
))}
</div>
);
};
Card.js - 接收 handleRemove 作为 props
const Card = ({product, handleRemove
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn && (
<button
onClick={() => handleRemove(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};...