rom
rom

Reputation: 664

Error "Maximum update depth exceeded. This can happen when a component calls setState inside useEffect"

I'm facing this error (repeats thousands of times until page crashes) whenever my Cart component (also shown below) is called:

index.js:1 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.
    in Cart (created by Context.Consumer)
    in Route (at Routes.js:24)
    in Switch (at Routes.js:23)
    in Router (created by BrowserRouter)
    in BrowserRouter (at Routes.js:22)
    in Routes (at src/index.js:5)

My Cart component:

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import Layout from "./Layout";
import { getCart } from "./cartHelpers";
import Card from "./Card";
import Checkout from "./Checkout";

const Cart = () => {
    const [items, setItems] = useState([]);

    useEffect(() => {
        setItems(getCart());
    }, [items]);

    const showItems = items => {
        return (
            <div>
                <h2>Your cart has {`${items.length}`} items</h2>
                <hr />
                {items.map((product, i) => (
                    <Card
                        key={i}
                        product={product}
                        showAddToCartButton={false}
                        cartUpdate={true}
                        showRemoveProductButton={true}
                    />
                ))}
            </div>
        );
    };

    const noItemsMessage = () => (
        <h2>
            Your cart is empty. <br /> <Link to="/shop">Continue shopping</Link>
        </h2>
    );

    return (
        <Layout
            className="container-fluid"
        >
            <div className="row">
                <div className="col-6">
                    {items.length > 0 ? showItems(items) : noItemsMessage()}
                </div>

                <div className="col-6">
                    <h2 className="mb-4">Your cart summary</h2>
                    <hr />
                    <Checkout products={items} />
                </div>
            </div>
        </Layout>
    );
};

export default Cart;

useEffect is calling getCart() (shown below):

export const getCart = () => {
    if (typeof window !== "undefined") {
        if (localStorage.getItem("cart")) {
            return JSON.parse(localStorage.getItem("cart"));
        }
    }
    return [];
};

I intend for getCart to grab the cart from the localStorage and populate that in the state variable items or return an empty array [].
From my understanding, useEffect changes the page whenever the state changes and when there is any item in the dependency array it will be based on that.
Well I cant figure out why this error is happening.
I really tried understanding this error 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 and here's what I learned so far trying to solve this:

  1. items, the dependency, doesn't change much. Testing adding 1 item or 0 items results in thousands of the same error
  2. Removing useEffect stops the error entirely but defeats the purpose of tying it to the items state variable

How can I stop this error?

I just want this component to work without freezing and throwing thousands of this error upon render.

Upvotes: 10

Views: 36258

Answers (4)

Akanksha Verma
Akanksha Verma

Reputation: 1

in my understanding by default useEffect run once when the program run so when it run, the setItems change the value of items as you see :

useEffect(() => {
    setItems(getCart());
}, [items]);

and when the items change useEffect again run that creates a loop, to stop it remove the dependencies.

useEffect(() => {
    setItems(getCart());
}, []);

Upvotes: 0

Justsolarry
Justsolarry

Reputation: 302

Problem is here:

useEffect(() => {
        setItems(getCart());
    }, [items]);

You have added a dependency with items. So that means whenever items changes, run setItems(getCart());

But when you set your items, that means the items change which in turn triggers the useEffect again and hence the infinite loop.

Solution:

useEffect(() => {
            setItems(getCart());
        }, []);

Remove the dependency. This means the useEffect will run as soon as the component is mounted and executed once only. If you want this to run for different reasons, you will need to add that to the dependency array.

Upvotes: 1

Dmitriy Kovalenko
Dmitriy Kovalenko

Reputation: 3626

The reason is with your useEffect dependencies:

useEffect(() => {
  setItems(getCart());
}, [items]);

The problem is that you are passing [items] and calling useEffect while items changed. And inside useEffect you are changing items.

Make sure if you return each time new object or array from getItems() react don't know that your objects are the same and calling effect again and again.

Solution

Remove items from dependencies

useEffect(() => {
  setItems(getCart());
}, []);

Or if you need to access current state while changing it:

useEffect(() => {
  setItems(currentItems => getCart(currentItems));
}, []);

Upvotes: 14

Dayachand Patel
Dayachand Patel

Reputation: 497

You should call setItems after getCard Promiss return or you can also use callback instead of promiss.

First you need need to take result and after that call setItems. It will resolve you problem.

Upvotes: 0

Related Questions