#javascript #reactjs #react-hooks #use-effect #use-state
#javascript #reactjs #реагирующие хуки #использование-эффект #состояние использования
Вопрос:
У меня есть компонент Cart и массив карточек внутри него.
Каждый раз, когда пользователь удаляет товар из корзины, я удаляю его из localstorage, а также удаляю из пользовательского интерфейса.
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 amp;amp; 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 amp;amp; (
<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]);
Комментарии:
1. измените массив зависимостей с
[items]
на[items.length]
2. Вы пробовали [ ] вместо [элементы]
3. @AtifSaddique: не работает, пользовательский интерфейс не обновляется
4. Причина, по которой он создает бесконечный цикл, заключается в том, что вы устанавливаете элементы внутри одного и того же эффекта, и он запускается при изменении значения элементов. Итак, как только вы попадаете внутрь эффекта, значение элементов изменяется, и эффект запускается снова, он снова меняет значение, запускается снова и так далее….
5. @FridayAmeh: Если мы это сделаем, пользовательский интерфейс никогда не обновляется
Ответ №1:
В 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 в качестве реквизита
const Card = ({product, handleRemove
.............
}) => {
const [redirect, setRedirect] = useState(false);
const [countHowManyCopies, setCountHowManyCopies] = useState(product.count);
const showRemoveButton = () => {
return (
showRemoveProductBtn amp;amp; (
<button
onClick={() => handleRemove(product._id)}
className='btn btn-outline-danger mt-2 mb-2'
>
Remove Product
</button>
)
)
};...
Ответ №2:
Я думаю, вы получите предупреждение «Превышена максимальная глубина обновления» при рендеринге компонента, не нужно ждать, чтобы нажать кнопку удаления. Причина в том, что setItems будет вызываться при изменении элементов, затем setItems изменит элементы, поэтому это вызовет бесконечную рекурсию.
useEffect(() => {
setItems(getCart());// setItems will change the items
}, [items]);
Anw, я думаю, что текущий подход не является чистым подходом, поскольку таким образом очень сложно контролировать повторный рендеринг компонента.
Мое предложение заключается в удалении использования useEffect, затем добавьте onRemoveItems в качестве реквизита CardComponent
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 amp;amp; (
<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;
Ответ №3:
Основная причина проблемы заключается в использовании ‘useEffect‘ для обновления состояния.
useEffect может использоваться только для выполнения побочных эффектов.
Побочный эффект — это действие, которое запускается в результате изменения состояния.
Вот некоторые примеры побочных эффектов:
- Отправьте форму после того, как пользователь предоставит входные данные.
- Извлекает данные JSON из API при загрузке страницы.
Обновление состояния внутри useEffect может вызвать бесконечный цикл.
Замеченная проблема является примером сценария обновления состояния внутри useEffect.
-
Всякий раз, когда вызывается setItems, состояние «элементов» изменяется.
-
Изменение состояния вызовет useEffect с целью запуска побочных эффектов.
-
useEffect снова вызывает setItems, что вызывает бесконечный цикл.
Как решить проблему?
Проблему можно решить, удалив хук useEffect, поскольку в данном сценарии это не требуется.