0

This is my product count code, by calling + button, it will add product object to the list of selectedFoods state; and decrease the amount of the products by calling - button.

I face an infinite loop when I want to remove the product item which has an amount of zero; by filtering this object from list of selected products leads to problem.

Here is what causes the problem

    if (newFood.amount < 1) {
        setSelectedFoods((prevFoods) => {
          return prevFoods.filter((food) => food.id !== newFood.amount.id);
        });
      }

How can I prevent this?

export const FoodContextProvider = (props) => {
  const [selectedFoods, setSelectedFoods] = useState([]);
  const [newFood, setNewFood] = useState({});
  const [mode, setMode] = useState(null);
  const [length, setLength] = useState(selectedFoods.length);

  useEffect(() => {
    if (mode === "add") {
      const duplicate = selectedFoods.some((item) => item.id === newFood.id);
      if (duplicate) {
        selectedFoods.forEach((item) => {
          if (item.id === newFood.id) {
            item.amount = newFood.amount;
          }
        });
      } else {
        setSelectedFoods((prevFoods) => {
          return [...prevFoods, newFood];
        });
      }
    } else {
      selectedFoods.forEach((item) => {
        if (item.id === newFood.id) {
          item.amount = newFood.amount;
        }
      });
      if (newFood.amount < 1) {
        setSelectedFoods((prevFoods) => {
          return prevFoods.filter((food) => food.id !== newFood.amount.id);
        });
      }
    }
    console.log(selectedFoods);
  }, [newFood, mode, selectedFoods, setSelectedFoods]);

  const selectingFood = (food) => {
    setLength((pre) => pre + 1);
    setNewFood(food.newFoodItem);
    setMode(food.mode);
  };

  return (
    <FoodContext.Provider
      value={{
        foodItems: foodItem,
        selectingFood: selectingFood,
        selectedFoods: selectedFoods,
        selectedItemLength: length,
      }}
    >
      {props.children} {/* App componnet */}
    </FoodContext.Provider>
  );
};
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • Does this answer your question? [Infinite loop in useEffect](https://stackoverflow.com/questions/53070970/infinite-loop-in-useeffect) – André May 19 '23 at 07:49

2 Answers2

3

There are a couple of issues with that useEffect (and some question about whether it should be an effect at all; more on that below):

  1. You're using selectedFoods as a dependency, but calling setSelectedFoods in the effect. That's what causes the infinite loop.

  2. You're modifying objects in selectedFoods instead of creating a new objects, which breaks the primary rule of state: Don't directly modify state items. More in the docs.

  3. You're modifying state based on existing state, but the existing state could be stale.

  4. Harmless, but: there's never any need to list useState state setter functions like setSelectedFoods in dependency arrays; they're guaranteed to be stable for the lifetime of the component instance.

Instead, pass a callback into setSelectedFoods so it calls your code with the up-to-date array. Then you don't need the array as an effect dependency, and you're never working with a stale copy. Something like this:

useEffect(() => {
    if (mode === "add") {
        // *** Using the callback form, we don't have a dependency on `selectedFoods` anymore
        setSelectedFoods((selectedFoods) => {
            const duplicate = selectedFoods.some(
                (item) => item.id === newFood.id
            );
            if (duplicate) {
                // *** Creating a new array
                return selectedFoods.map((item) => {
                    if (item.id === newFood.id) {
                        // *** Creating a new object
                        item = { ...item, amount: newFood.amount };
                    }
                    return item;
                });
            } else {
                setSelectedFoods((prevFoods) => {
                    return [...prevFoods, newFood];
                });
            }
        });
    } else {
        // *** Again, using the callback form
        setSelectedFoods((selectedFoods) => {
            if (newFood.amount < 1) {
                return selectedFoods.filter(
                    (food) => food.id !== newFood.amount.id
                );
            } else {
                // *** Creating a new array
                return selectedFoods.map((item) => {
                    if (item.id === newFood.id) {
                        // *** Creating a new object
                        item = { ...item, amount: newFood.amount };
                    }
                });
            }
        });
    }
}, [newFood, mode]);

Now the effect is only triggered when newFood or mode changes.

But stepping back: It seems very odd to have an effect adding and removing items from your selectedFoods array. That seems like something that should be happening as a direct action, not as an effect, though of course it's hard to say without a broader context.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
-1

You shouldnt add a lot of dependencies on useEffect, break it to multiple useEffect and verify

Mithun Shreevatsa
  • 3,588
  • 9
  • 50
  • 95