Mr. Engineer
Mr. Engineer

Reputation: 375

Redux: accessing dictionary as state object

I have implemented a shopping cart using Redux, I have used a dictionary as state object (product id being the key and quantity in cart being the value). Here is how my cart.js looks like:

import React from 'react';
import ReactDOM from 'react-dom';

export const AddItemToCart = (productID) => {
    return {
        type: 'ADDITEMTOCART',
        productID
    }
}
export const DeleteItemFromCart = (productID) => {
    return {
        type: 'DELETEITEMFROMCART',
        productID
    }
}
export const Counter = (state = {}, action) => {
    switch (action.type) {
        case 'ADDITEMTOCART':
            console.log(action);
            return {
              ...state,
              [action.productID]: ( state[action.productID] || 0 ) + 1
            }
        case 'DELETEITEMFROMCART':
            return {
              ...state,
              [action.productID]: ( state[action.productID] || 1 ) - 1
            }
    }
  }

I'm adding an item from App.js like this:

return products.map(products =>
      <div key={products.ProductID}>
        <h2>{products.ProductName}</h2>
        <h2>{products.ProductDescription}</h2>
        <h2>{products.ProductQuantity} units available</h2>
        
        <button onClick={() => { store.subscribe(() => console.log(store.getState()));
      store.dispatch(AddItemToCart(products.ProductID));}}>Add to cart</button>
        </div>

Everything is working just fine but the problem is, I can't render the contents of the cart for user to see. I have tried:

function ShowCartContents() {
  var items = Object.keys(store.getState()).map(function(key){
    return store.getState()[key];
});
  return (
    <div>
        <h2>{items}</h2>
      </div>
  );
}

This function throws exception when called:

TypeError: Cannot convert undefined or null to object

Clearly the store itself is not null or undefined, because the change of state is successfully printed to the browser console. So, how would I access all the values in dictionary without keys? And how would I access one specific value by key? Any advise would be highly appreciated. Thanks.

Upvotes: 0

Views: 1111

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42188

Your Counter reducer has no default case, so your state will be undefined on the first render.

That's the source of your error "TypeError: Cannot convert undefined or null to object".

You need to return the existing state when neither action matches. Every reducer needs a default case because they will be called with actions like the {type: '@@redux/INIT'} action which is used to initialize the store.

default:
  return state;

You are trying to access the store directly with store.subscribe(), store.getState() and store.dispatch(). This is not the correct way to interact with a Redux store in React. You should use the react-redux package.


You want to wrap your entire app in a Provider component that provides the store instance. Something like this:

import { render } from "react-dom";
import { Provider } from "react-redux";

import App from "./components/App";
import store from "./store";

const rootElement = document.getElementById("root");
render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In your components, use the hook useSelector to select values from the state and useDispatch to access the dispatch function. (You can also use the connect higher-order component, but the hooks are preferred).

I'm not sure if this reducer is your entire state or if you are using combineReducers and have multiple reducers like cart, products, etc. This selector is assuming that it's the entire state.

function ShowCartContents() {
  const productIds = useSelector(state => Object.keys(state))
  
  return (
    <div>
        <h2>Ids In Cart: {productIds.join(", ")}</h2>
      </div>
  );
}
function ProductsList({ products }) {
  const dispatch = useDispatch();

  return (
    <div>
      {products.map((product) => (
        <div key={product.ProductID}>
          <h2>{product.ProductName}</h2>
          <h2>{product.ProductDescription}</h2>
          <h2>{product.ProductQuantity} units available</h2>

          <button onClick={() => dispatch(AddItemToCart(product.ProductID))}>
            Add to cart
          </button>
        </div>
      ))}
    </div>
  );
}

Upvotes: 1

Related Questions