useEffect запускает бесконечные рендеры в React (превышена максимальная глубина обновления)

#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, поскольку в данном сценарии это не требуется.