Search code examples
javascriptreduxredux-toolkit

Redux-tookit: Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers


Error type: Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.

I am trying to add a Store and my app is failing. I don't understand where the error is coming from. Can someone point out my error and how to fix it?

store

import { configureStore } from '@reduxjs/toolkit';
import rootReducers from './reducers';

const store = configureStore({
  reducer: rootReducers,
});

export default store;

reducer

import handleCart from './handleCart';
import { combineReducers } from 'redux';

const rootReducers = combineReducers({
  handleCart,
});

export default rootReducers;
const cart = [];

const handleCart = (state = cart, action) => {
  const product = action.payload;
  switch (action.type) {
    case ADDITEM:
      // Check product exist
      const exist = state.find(x => x.id === product.id);
      if (exist) {
        return state.map(x =>
          x.id === product.id ? {...x, qty: x.qty + 1} : x,
        );
      } else {
        const product = action.payload;
        return [
          ...state,
          {
            ...product,
            qty: 1,
          },
        ];
      }
      break;
    case REMOVEITEM:
      const exist1 = state.find(x => x.id === product.id);
      if (exist1.qty === 1) {
        return state.filter(x => x.id !== exist1.id);
      } else {
        return state.map(x =>
          x.id === product.id ? {...x, qty: x.qty - 1} : x,
        );
      }
      break;
    default:
      break;
  }
};

Solution

  • I see only a couple overt issues in the handleCart reducer function code, either could be cause for throwing an exception. It seems the handleCart reducer function is missing returning state in the default case. The REMOVEITEM case also incorrectly assumes exist is defined and accesses a qty property of a potentially undefined object. Remember that Array.prototype.find returns undefined when no matching element is found in the array.

    Make sure the handleCart is default exported so it can be imported to create the root reducer.

    const cart = [];
    
    const handleCart = (state = cart, action) => {
      const product = action.payload;
    
      switch (action.type) {
        case ADDITEM: {
          // Check product exist
          const exist = state.find(x => x.id === product.id);
    
          if (exist) {
            return state.map(x =>
              x.id === product.id ? { ...x, qty: x.qty + 1 } : x,
            );
          }
          return [...state, { ...product, qty: 1 }];
        }
    
        case REMOVEITEM: {
          const exist = state.find(x => x.id === product.id);
    
          if (exist) {
            // item exists, update quantity
            if (exist.qty === 1) {
              return state.filter(x => x.id !== exist1.id);
            } else {
              return state.map(x =>
                x.id === product.id ? { ...x, qty: x.qty - 1 } : x,
              );
            }
          }
          // item didn't exist, just return current state
          return state;
        }
    
        default:
          // No action to do, return current state
          return state;
      }
    };
    
    export default handleCart;
    

    You are using redux-toolkit, why are you not creating state slice? Here's a similar implementation using createSlice. This allows you to write the reducer functions using mutable updates instead of needing to shallow copy all the parts of the state that are being updated.

    import { createSlice } from '@reduxjs/toolkit';
    
    const initialState = [];
    
    const cartSlice = createSlice({
      name: "cart",
      initialState,
      reducers: {
        addItem: (state, action) => {
          const product = state.find(product => product.id === action.payload.id);
    
          if (product) {
            product.qty++;
          } else {
            state.push({ ...action.payload, qty: 1 });
          }
        },
        removeItem: (state, action) => {
          const product = state.find(product => product.id === action.payload.id);
    
          if (product) {
            product.qty--;
            if (product.qty === 0) {
              state.filter(product => product.id !== action.payload.id);
            }
          }
        },
      },
    });
    
    export const { addItem, removeItem } = cartSlice.actions;
    
    export default cartSlice.reducer;