javascriptreactjsreduxreact-reduxredux-toolkit

Error While using Redux - Uncaught TypeError: Cannot read properties of undefined (reading 'items')


I'm trying to access states from the redux store using useSelector(), but instead I get the following error-

AddToCart.jsx:7 Uncaught TypeError: Cannot read properties of undefined (reading 'items')
    at AddToCart.jsx:7:58
    at react-redux.js?v=afc4f5cf:189:28
    at memoizedSelector (react-redux.js?v=afc4f5cf:46:38)
    at getSnapshotWithSelector (react-redux.js?v=afc4f5cf:74:22)
    at mountSyncExternalStore (react-dom_client.js?v=afc4f5cf:11886:28)
    at Object.useSyncExternalStore (react-dom_client.js?v=afc4f5cf:12573:22)
    at useSyncExternalStore (chunk-4HAMFFQC.js?v=afc4f5cf:1120:29)
    at useSyncExternalStoreWithSelector3 (react-redux.js?v=afc4f5cf:81:23)
    at useSelector2 (react-redux.js?v=afc4f5cf:243:27)
    at AddToCart (AddToCart.jsx:7:19)

store.js

import { configureStore } from '@reduxjs/toolkit';
import itemcountSliceReducer from './itemcountSlice'

const store = configureStore({
  reducer: {
    itemcountSliceReducer,
  }
})

export default store;

itemcountSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  items: {
    1: 0,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: 0,
    7: 0,
    8: 0,
    9: 0,
  },
  totalAmount: 0,
}

const itemcountSlice = createSlice({
  name: "itemcount",
  initialState,
  reducers: {
    addItem: (state, action) => {
      state.items = {
        ...state.items, 
        [action.payload.id]: state.items[action.payload.id] + 1
      }
    },
    rmvItem: (state, action) => {
      state.items = {
        ...state.items, 
        [action.payload.id]: state.items[action.payload.id] - 1
      }
    }
  }
})

export const { addItem, rmvItem } = itemcountSlice.actions;
export default itemcountSlice.reducer;

AddToCart.jsx

import React from 'react'
import { useSelector,useDispatch } from 'react-redux'
import {addItem} from '../store/itemcountSlice'

const AddToCart = ({ id }) => { 
  const count = useSelector((state) => state.itemcount.items[id])
  const dispatch = useDispatch()

  return (
    <button onClick={(e) => dispatch(addItem(id))}>
      <img
        src = '../../assets/images/icon-add-to-cart.svg'
        alt = 'add to cart'
      />
      <span>Add to Cart</span>
    </button>
  )
}

export default AddToCart

App.jsx

import { useState, useEffect } from 'react'
import './App.css'
import cardsData from '../data.json'
import Card from './components/Card'

function App() {
  const data = cardsData.map((item, i) => (
    { ...item, id: i + 1 }
  ))

  return (
    <div className='main-container'>
      <div className='left-container'>
        <h1>Desserts</h1>
        <div className='cards-container' >
          {data.map((item) => 
            (<Card image = {item.image.desktop}
                category = {item.category}
                name = {item.name}
                price = {item.price} 
                key = {item.id} 
                id = {item.id}           
            />))}
        </div>
      </div>
      <div className='right-container'></div>
    </div>
  )
}

export default App

My App component is wrapped in a Provider tag, so I'm not sure what the problem is. Is there a syntax issue when I'm using useSelector, or have I not added the reducers to the store in the correct manner.


Solution

  • Issue

    You are selecting from state.itemcount but this Redux state property does not exist since you named it itemcountSliceReducer when creating the store.

    const store = configureStore({
      reducer: {
        itemcountSliceReducer, // <-- state.itemcountSliceReducer
      }
    })
    

    Solution

    The state you are creating must match the state you are selecting.

    Either rename the root state to itemCount like you are selecting:

    const store = configureStore({
      reducer: {
        itemCount: itemcountSliceReducer, // <-- state.itemcount
      }
    })
    
    const count = useSelector((state) => state.itemcount.items[id]);
    

    Or update the selector function to match the root state identifier:

    const store = configureStore({
      reducer: {
        itemcountSliceReducer, // <-- state.itemcountSliceReducer
      }
    })
    
    const count = useSelector((state) => state.itemcountSliceReducer.items[id]);
    

    Suggestion/Improvement

    Unrelated, but you'll also want to get into the habit of creating memoized selectors for the id value you are using in the selector function.

    Example:

    import { createSelector } from '@reduxjs/toolkit';
    
    const selectItemCounts = (state) => state.itemcount.items;
    
    const selectItemCountById = createSelector(
      [
        selectItemCounts,
        (state, id) => id,
      ],
      (items, id) => items[id]
    );
    
    const count = useSelector((state) => selectItemCountById(state, id);
    

    You also don't need to shallow copy the states, you can directly mutate them if you like since RTK utilizes Immer.js under-the-hood, i.e. it handles the shallow copying of the draft changes to the next state value.

    Example:

    const itemcountSlice = createSlice({
      name: "itemcount",
      initialState,
      reducers: {
        addItem: (state, action) => {
          state.items[action.payload.id]++;
        },
        rmvItem: (state, action) => {
          state.items[action.payload.id]--;
        }
      }
    })