C-Bizz
C-Bizz

Reputation: 654

Redux action not updating redux state in react-redux

Whenever I click add to cart button, the action is fired but redux state is not been updated (the initial state is not changing but the action is triggered).

const CartScreen = () => {
    const { id } = useParams();
    const { search } = useLocation();
    const [searchParms] = useSearchParams();
  
    const productId = id;
    const qty = search ? Number(search.split("=")[1]) : 1;
  

    const dispatch = useDispatch()

    useEffect(() => {
      if (productId){
        dispatch(addToCart(productId, qty))
      }
    
    }, [dispatch, productId, qty])

    
    return (
      <div>
        <h1>Add to CART</h1>
      </div>
    );
  };
  
  export default CartScreen

Cart action

export const addToCart = (id, qty) => async (dispatch, getState) =>{
    const {data} = await axios.get(`http://127.0.0.1:8000/api/products/${id}`)
    dispatch({
        type: CART_ADD_ITEM,
        payload:{
            product:data._id,
            name:data.name,
            image:data.image,
            countInStock:data.countInStock,
            qty
        }
    })
    localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems))
}

Cart Reducer

export const cartReducer = (state = { cartItems: []}, action) =>{
    switch(action.type){
        case CART_ADD_ITEM:
            const item = action.payload
            const existItem = state.cartItems.findIndex(x => x.product === item.product)

            if (existItem){
                return{
                    ...state,
                    cartItems: state.cartItems.map(x => 
                        x.product === existItem.product ? item : x)
                }
            } else{
                return{
                    ...state,
                    cartItems:[...state.cartItems, item]
                }
            }

            default:
                return state
    }
}

Redux store

const reducer = combineReducers({
    productList: productListReducer,
    productDetails: productDetailsReducer,
    cart: cartReducer,

})

const initialState = {
  cart:{cartItems:cartItemsFromStorage}
};
const middleware = [thunk];

 const store = createStore(
  reducer,
  initialState,
  composeWithDevTools(applyMiddleware(...middleware))
);

From redux dev tools I can see that the action I triggered. The item is getting to cart reducer because when I console.log item in const item=action.payload from the cartReducer, I get the particular item in Browser console, yet the cartItem redux state remains at the initial value, it's not updated

Upvotes: 0

Views: 1313

Answers (2)

Drew Reese
Drew Reese

Reputation: 202605

The issue is that you are searching for an existing product and returning the found index (array.findIndex) then using the index value as a boolean condition (if (existItem) {...}).

This won't work as you are expecting since all non-zero numbers are truthy, while 0 is falsey. This means if no cart item products match that -1 is returned and the logic will treat this as an existing item. This is compounded later when updating the cart via array.map... if existItem is -1 this means there is no matching product, the new state.cartItems will be a new array, but it will not contain the new item object. In other words it will be just a copy of the previous state.

cartItems starts initially as an empty array, so existItem will always return -1 when first adding an item to the cart.

An additional unintentional bug occurs when a product does exist in the cartItems array and it's the 0th element. existItem will equal 0 and is thus falsey and item will be added to cartItems array as a duplicate.

@Chigbogu is correct regarding the use of array.findIndex and array.find, though I'd recommend using array.some if you are just checking the the cart items array has the item or not. This indicates you are working with a boolean explicitly. Rename existItem to hasItem or similar so the name also indicates a boolean value (this is by convention).

export const cartReducer = (state = { cartItems: []}, action) =>{
  switch(action.type) {
    case CART_ADD_ITEM: {
      const newItem = action.payload;
      const hasItem = state.cartItems.some(item => item.product === newItem.product);

      if (hasItem) {
        return {
          ...state,
          cartItems: state.cartItems.map(item => 
            item.product === newItem.product ? newItem : item
          )
        }
      } else {
        return {
          ...state,
          cartItems: [...state.cartItems, newItem]
        }
      }
    }

    default:
      return state;
  }
};

Upvotes: 0

Chigbogu
Chigbogu

Reputation: 24

Array.prototype.find()- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Array.prototype.findIndex()- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Using Array.prototype.findIndex() will basically look for and return the index of the first found item, and -1 if not found. While Array.prototype.find() returns the first element in the array that matched the criteria provided.

export const cartReducer = (state = { cartItems: [] }, action) => {
  switch(action.type){
  case CART_ADD_ITEM:
    const item = action.payload;

    // use Array.prototype.find() instead
    // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
    const existItem = state.cartItems.find(x => x.product === item.product);

    if (existItem){
      return{
        ...state,
        cartItems: state.cartItems.map(x =>
          x.product === existItem.product ? item : x)
      };
    } else{
      return{
        ...state,
        cartItems: [...state.cartItems, item]
      };
    }

  default:
    return state;
  }
};

Upvotes: 1

Related Questions