G T
G T

Reputation: 51

useEffect not firing after updating the component's state

I am making a simple e-commerce website but I've ran into an issue where useEffect() won't fire after making a state change. This code snippet I'll include is for the "shopping cart" of the website and uses localStorage to store all items in the cart. My state will change when quantity changes in the QuantChange() function but will not trigger useEffect(). When I refresh the page after changing an item's quantity, the new quantity won't persist and the old quantity is shown instead. What am I doing wrong? Thanks in advance.

import React, { useState, useEffect } from 'react';
import { SetQuantity } from '../utils/Variables';
import { CartItem } from './CartItem';

const CartView = () => {
    const [state, setState] = useState(
        JSON.parse(localStorage.getItem('cart-items'))
            ? JSON.parse(localStorage.getItem('cart-items'))
            : []
    );

    useEffect(() => {
        console.log('Updating!');
        updateLocalStorage();
    });

    const updateLocalStorage = () => {
        localStorage.setItem('cart-items', JSON.stringify(state));
    };

    const quantChange = (event) => {
        setState((prevState) => {
            prevState.forEach((item, index) => {
                if (item._id === event.target.id) {
                    item.quantity = SetQuantity(parseInt(event.target.value), 0);
                    prevState[index] = item;
                }
            });

            return prevState;
        });
    };

    const removeItem = (id) => {
        setState((prevState) => prevState.filter((item) => item._id != id));
    };

    // Fragments need keys too when they are nested.
    return (
        <>
            {state.length > 0 ? (
                state.map((item) => (
                    <CartItem
                        key={item._id}
                        ID={item._id}
                        name={item.name}
                        quantity={item.quantity}
                        changeQuant={quantChange}
                        delete={removeItem}
                    />
                ))
            ) : (
                <h1 className="text-center">Cart is Empty</h1>
            )}
        </>
    );
};

export default CartView;

import React, { Fragment } from 'react';
import { MAX_QUANTITY, MIN_QUANTITY } from '../utils/Variables';

export const CartItem = (props) => {
    return (
        <>
            <h1>{props.name}</h1>
            <input
                id={props.ID}
                type="number"
                max={MAX_QUANTITY}
                min={MIN_QUANTITY}
                defaultValue={props.quantity}
                onChange={props.changeQuant}
            />
            <button onClick={() => props.delete(props.ID)} value="Remove">
                Remove
            </button>
        </>
    );
};
export const MIN_QUANTITY = 1;

export const MAX_QUANTITY = 99;

// Makes sure the quantity is between MIN and MAX
export function SetQuantity(currQuant, Increment) {
    if (Increment >= 0) {
        if (currQuant >= MAX_QUANTITY || (currQuant + Increment) > MAX_QUANTITY) {
            return MAX_QUANTITY;
        } else {
            return currQuant + Increment;
        }
    } else {
        if (currQuant <= MIN_QUANTITY || (currQuant + Increment) < MIN_QUANTITY) {
            return MIN_QUANTITY;
        } else {
            return currQuant + Increment;
        }
    }
}

Upvotes: 4

Views: 2559

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

You are not returning new state, you are forEach'ing over it and mutating the existing state and returning the current state. Map the previous state to the next state, and for the matching item by id create and return a new item object reference.

const quantChange = (event) => {
  const { id, value } = event.target;
  setState((prevState) => {
    return prevState.map((item) => {
      if (item._id === id) {
        return {
          ...item,
          quantity: SetQuantity(parseInt(value), 0)
        };
      }
      return item;
    });
  });
};

Then for any useEffect hook callbacks you want triggered by this updated state need to have the state as a dependency.

useEffect(() => {
  console.log('Updating!');
  updateLocalStorage();
}, [state]);

Upvotes: 4

Related Questions