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.
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
}
})
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]);
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]--;
}
}
})