Reputation: 375
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
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