I am struggling with the function of my reducer to add an item and increase its quantity if already present in cart.
What my code does so far is adding another "quantity" with 1 instead of updating the quantity already present in my state.
Here's my code :
reducer :
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
if (state.findIndex(el => el.item.title === action.item.title) === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
return [...state, { quantity: action.quantity + 1 }];
}
default:
return state;
}
}
action :
import { ADD_TO_CART } from "./types";
import axios from "axios";
export const addToCart = id => dispatch => {
axios
.get(`https://api.itbook.store/1.0/search/${id}`)
.then(items =>
items.map((item, quantity) =>
dispatch({
type: ADD_TO_CART,
item,
quantity
})
)
);
};
Thanks
You are finding the index (which is great) but not doing anything with it (which is not so great):
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
// Use map to create a new state object
return state.map((item, i) =>
index === i //Only modify the found index
? { ...item, quantity: item.quantity + action.quantity } //Add the required quantity to the current quantity (not too sure about this)
: item //Don't modify other items
);
}
default:
return state;
}
}
import { ADD_TO_CART } from "../actions/types";
export default function (state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index > -1) {
const newState = [...state];
newState[index] = { ...newState[index], quantity: action.quantity + 1 };
return newState;
} else {
return [...state, { ...action.item, quantity: action.quantity + 1 }];
}
default:
return state;
}
}
Related
I have a Card component that shows a product and then a compare check box. If this is checked then the the item should reflect that in all areas it is shown (with a checked box). This is fine from page to page however there are a few spots that item may be displayed on a carousel and on a list for relative items. So then when that list item is clicked the same item in the carousel on the same 'page' does not update until we refresh. The code is fairly large but we have a button component inside the card component which has a function in the useEffect to watch for changes (which works fine until the item is shown twice on one page) but again it wont update unless we refresh.
I'm sharing the code for the compare button... that's really where I feel the changes are needed:
BUTTON COMPONENT
export interface LikeCompareButtonProps {
products: any[];
}
export default function LikeCompareButton({ products }: LikeCompareButtonProps) {
const dispatch = useDispatch();
const [checked, setChecked] = useState(false);
const checkProducts: any[] = useAppSelector((state) => state?.compareItems?.products)
|| [];
const checkForChange = (products: any) => {
if (compareView) {
setChecked(true);
}
if (!compareView) {
const checked = checkProducts?.find((x) => x.id === products?.id);
if (checked) {
setChecked(true);
} else {
setChecked(false);
}
}
};
const handleChange = (products: {}) => {
if (!checked) {
dispatch(setCompareItem(products));
dispatch(compareItemIncreaseCounter());
setChecked(true);
}
if (checked) {
dispatch(deleteCompareItem(products));
dispatch(compareItemDecreaseCounter());
setChecked(false);
}
getCompareItem();
};
useEffect(() => {
dispatch(getCompareItem());
dispatch(getCompareCounter());
checkForChange(products);
}, [checked]);
// hoping checked here would trigger every time it changes....
// parent component is card one. it maps through an Array -> products
return (
<div>
<button onClick={() => handleChange(products)}>
<div>
{checked && <CheckIcon className="text-white font-bold absolute h-4 w-4" />}
</div>
{!checked ? "Compare" : "Remove"}
</button>
</div>
);
}
CARD COMPONENT import Link from "next/link";
import LikeCompareButton from "../../button/like-compare-button";
export interface SmallProductCardProps {
products: any[];
}
export default function SmallProductCard({products}:
SmallProductCardProps) {
return (
<>
{products?.map((product: any, index: number) => (
<div key={product.id} >
<Link
href={
pathname: `/store/category/d/${product.id}`,
query: { item: product.id }
}}>
<a>
<img
src="http://www.innovativeengsystems.com/wp-c
ontent/uploads/2017/11/no-img-portrait.png"
alt={product.id}
/>
)}
</a>
</Link>
<LikeCompareButton
products={products[index]}
/>
</div>
))}
</>
);
}
HOME: WHERE PRODUCTS IS DEFINED
const Home: NextPage = () => {
const dispatch = useDispatch();
const products: any[] = useAppSelector((state) =>
state?.products?.products?.data);
useEffect(() => {
dispatch(getProductList()); //API RENDERED DATA
}, []);
return (
<>
<SectionLayout hero={true}>
<InnerGridViewLayout colSize={"col-span-4 grid-cols-2 grid-
rows-2 pt-0"}>
<SmallProductCard
products={products}
/>
</InnerGridViewLayout>
</SectionLayout>
</>
);
};
export default Home;
Adding REDUX work
ACTIONS
export const setCompareItem = (products: any) => {
return {
type: SET_COMPARE_ITEM,
payload: products
};
};
export const setCompareItemSuccessful = (products: any) => {
localStorage.setItem("item", JSON.stringify(products.payload));
const existingItems: any[] =
JSON.parse(localStorage.getItem("compareItems")!) ?? [];
existingItems?.push(products.payload);
localStorage.setItem("compareItems",
JSON.stringify(existingItems));
return {
type: SET_COMPARE_ITEM_SUCCESSFUL,
payload: products
};
};
export const getCompareItem = () => {
return {
type: GET_COMPARE_ITEM,
payload: []
};
};
export const getCompareItemSuccessful = () => {
var products: any[] = JSON.parse(localStorage.getItem("compareItems")!);
return {
type: GET_COMPARE_ITEM_SUCCESSFUL,
payload: products
};
};
REDUCER
import {
ERROR_COMPARE,
SET_COMPARE_ITEM,
SET_COMPARE_ITEM_SUCCESSFUL,
GET_COMPARE_ITEM,
GET_COMPARE_ITEM_SUCCESSFUL,
DELETE_COMPARE_ITEM
} from "./actionTypes";
const compareItemsState = {
products: [],
loading: false,
message: ""
};
const reducer = (state = compareItemsState, action) => {
switch (action.type) {
case SET_COMPARE_ITEM:
state = {
...state,
products: [action.payload],
message: "item added"
};
break;
case SET_COMPARE_ITEM_SUCCESSFUL:
state = {
...state,
products: [action.payload],
message: "set item success"
};
break;
case GET_COMPARE_ITEM:
state = {
...state,
products: action.payload,
message: "item found success"
};
break;
case GET_COMPARE_ITEM_SUCCESSFUL:
state = {
...state,
products: action.payload,
message: "found item"
};
break;
case DELETE_COMPARE_ITEM:
state = {
...state,
products: action.payload,
message: "item deleted"
};
break;
case ERROR_COMPARE:
state = {
...state,
compareCounter: state.compareCounter,
message: "Error adding message"
};
break;
default:
state = { ...state };
break;
}
return state;
};
export default reducer;
SAGA.tsx
import { takeEvery, fork, put, all, call } from "redux-
saga/effects";
import {
SET_COMPARE_ITEM,
SET_COMPARE_ITEM_SUCCESSFUL,
DELETE_COMPARE_ITEM,
GET_COMPARE_ITEM_SUCCESSFUL,
GET_COMPARE_ITEM,
} from "./actionTypes";
import {
compareError,
setCompareItem,
setCompareItemSuccessful,
deleteCompareItem,
getCompareItem,
getCompareItemSuccessful,
} from "./actions";
function* setCompareItems(product: any) {
try {
yield put(setCompareItem(product));
} catch (error: any) {
yield put(compareError("Item not added, please try again."));
}
}
function* setCompareItemsSuccessful(products: any) {
try {
yield put(setCompareItemSuccessful(products));
} catch (error: any) {
yield put(compareError("Item not removed, please try
again."));
}
}
function* getCompareItems() {
try {
yield put(getCompareItem());
} catch (error: any) {
yield put(compareError("please try again."));
}
}
function* getCompareItemsSuccessful() {
try {
yield put(getCompareItemSuccessful());
} catch (error: any) {
yield put(compareError("please try again."));
}
}
function* setDeleteItems(products: any) {
try {
yield put(deleteCompareItem(products));
} catch (error: any) {
yield put(compareError("Item removed"));
}
}
export function* watchSetCompareItemSuccess() {
yield takeEvery(SET_COMPARE_ITEM_SUCCESSFUL, setCompareItems);
}
export function* watchSetCompareItem() {
yield takeEvery(SET_COMPARE_ITEM, setCompareItemsSuccessful);
}
export function* watchGetCompareItemSuccess() {
yield takeEvery(GET_COMPARE_ITEM_SUCCESSFUL, getCompareItems);
}
export function* watchGetCompareItem() {
yield takeEvery(GET_COMPARE_ITEM, getCompareItemsSuccessful);
}
export function* watchDeleteCompareItem() {
yield takeEvery(DELETE_COMPARE_ITEM, setDeleteItems);
}
function* compareItemStoreSaga() {
yield all([
fork(watchSetCompareItem),
fork(watchGetCompareItem),
]);
}
export default compareItemStoreSaga;
I understand that in Strict Mode, the reducer should run twice. However, it shouldn't actually update the values twice.
The quantity for items gets updated twice.
For example, there is an item, items: [{name: tshirt, price: 10, quantity: 1}] already in the cart. If call addItem(state, tshirt, 1), the cart will update to items: [{name: tshirt, price: 10, quantity: 3}].
Only the quantity value inside the items array is updated twice. The outside variables such as value and total_qty only update once.
How do I stop it updating twice without turning off StrictMode?
interface Product {
name: string,
materials: string[],
categories: string[],
price: number,
image?: string
}
interface ICartItem extends Product {
quantity: number
}
interface Cart {
items: {[key: string]: ICartItem},
value: number,
total_qty: number
}
const addItem = (state: Cart, product: Product, quantity: number) => {
let item = state?.items?.[product.name];
if (item) {
item.quantity += quantity;
} else {
item = {
...product,
quantity
}
}
let updatedCart = {
...state,
items: {
...state.items,
[product.name]: item
},
value: Math.max(0, state.value + (product.price * quantity)),
total_qty: Math.max(0, state.total_qty + quantity)
}
return updatedCart;
}
const cartReducer: Reducer<Cart, UpdateCartAction> = (state: Cart, action: UpdateCartAction) => {
switch (action.type) {
case 'ADD_ITEM':
return addItem(state, action.product, action.quantity);
case 'REMOVE_ITEM':
return removeItem(state, action.product, action.quantity);
case 'CLEAR_CART':
return clearCart();
default:
return state;
}
}
export const CartContext = React.createContext<ICartContext | undefined>(undefined);
export const CartProvider = ({children}: {children: ReactNode}) => {
const {cart, dispatch} = useLocalStorageReducer(
'cart',
cartReducer,
initialCart
);
const contextValue = useMemo(()=>{
return {cart, dispatch}
}, [cart]);
return (
<CartContext.Provider value={contextValue}>{children}</CartContext.Provider>
)
}
export const useCart = () => {
const contextValue = useContext(CartContext);
let cart: Cart | undefined, dispatch: Dispatch<any> | undefined;
if (contextValue) {
cart = contextValue.cart;
dispatch = contextValue.dispatch;
}
const addItem = (product: Product, quantity: number) => {
if (dispatch) dispatch({type: "ADD_ITEM", product, quantity});
}
return {
cart,
addItem
}
}
I am playing around with a redux tutorial code, I am trying to add another number value to createPolicy action creator, it is showing some error, please tell me what am I missing here
console.clear()
const createPolicy = (name, amount) => {
return {
type : 'CREATEPOLICY',
payload : {
name, amount, bill
}
}
}
const deletePolicy = (name) => {
return {
type : 'DELETEPOLICY',
payload : {
name
}
}
}
const claimRequest = (name, amountofclaim, fees) => {
return {
type : 'CLAIMREQUEST',
payload : {
name, amountofclaim, fees
}
}
}
const creteReducer = (previesdelte = [], action) => {
if(action.type === "CREATEPOLICY") {return [...previesdelte, action.payload ]}
else{return previesdelte}
}
const clamReducer = (totalamount=200, action) => {
if(action.type === "CLAIMREQUEST") {
return totalamount - (action.payload.amountofclaim + action.payload.fees)}
else if(action.type === "CREATEPOLICY") {
return totalamount + (action.payload.amount + action.payload.bill)}
else{return totalamount}
}
const deleteReducer = (userlist = [], action) => {
if(action.type === "CREATEPOLICY"){
return [...userlist, action.payload]
}
else if(action.type === "DELETEPOLICY") {
return userlist.filter( name => name !== action.payload.name)
}
else {
return userlist
}
}
const {createStore, combineReducers} = Redux;
const allReducers = combineReducers({
creteReducer:creteReducer,
clamReducer:clamReducer,
deleteReducer:deleteReducer
})
const store = createStore(allReducers)
store.dispatch(createPolicy('fff', 20, 3))
store.dispatch(claimRequest('fff', 150, 5))
console.log(store.getState())
A parameter bill is probably missing from your createPolicy function.
const initialState = {
cart: []
}
export default function(state = initialState, action){
switch(action.type){
case 'CART_ADDITEM':
return {
...state,
cart: [...state.cart, action.payload]
}
case 'CART_REMOVEITEM':
return {
...state,
cart: state.cart.filter(item => item !== action.payload)
}
break;
}
return state;
}
here's my add/remove to array functions
this is the object in the array.
id(pin): "001"
Name(pin): "Dab on them"
price(pin): 100
img(pin): "/static/media/001.ac043cfc.png"
rarity(pin): "rare"
size(pin): "S"
quantity(pin): 1
how to check when adding a new item to the array if object match and if it does update quantity + 1.
same thing goes for removing item from array if quantity > 1 then quantity -1
You can check if is already present in the array an object with the same id and in which quantity. In that case update the quantity, otherwise add the object to the array.
I would write few helper methods to keep everything clear and separate.
You can check for presence by quantity with something like this:
const quantityForItem = (list, newItem) => {
const item = list.find(item => item.id === newItem.id)
return item && item.quantity || 0
}
You can increase or decrease the quantity of an item remapping the list and alterating the interested item:
const updateQuantity = (list, newItem, variation) =>
list.map(item => {
if ( item.id !== newItem.id ) return item
return { ...item, quantity: item.quantity + variation }
})
You can add a new item just concating it to the previous list. Pay attention: concat, not push, because we need to keep it immutable.
const add = (list, newItem) =>
list.concat(newItem)
The removal of an item could be like the one you already have:
const remove = (list, itemToRemove) =>
list.filter(item => item.id !== itemToRemove.id)
Putting everything together:
case 'CART_ADDITEM': {
const { cart } = state
const newItem = action.payload
if ( quantityForItem(cart, newItem) !== 0 ) {
return { cart: updateQuantity(cart, newItem, +1) }
}
return { cart: add(cart, newItem) }
}
case 'CART_REMOVEITEM': {
const { cart } = state
const itemToRemove = action.payload
if ( quantityForItem(cart, itemToRemove) > 1 ) {
return { cart: updateQuantity(cart, itemToRemove, -1) }
}
return { cart: remove(cart, itemToRemove) }
}
Help me please solve this issue.
I use redux and react-redux to control state in my app.
But when I try to change styles in my Component depending in the value from redux store, it react with delay. When I add new Item and click the list and expect its color being changed, it does this only after I add another item, so that it always delays.
Here is my reducer
export const items = (state = [], action) => {
switch(action.type) {
case 'ADD_ITEM':
const newItem = {
title: action.title,
id: action.id,
selected: action.selected,
comments: action.comments
};
return [
...state,
newItem
];
case 'REMOVE_ITEM':
return state.filter(({ id }) => id !== action.id);
case 'SELECT_ITEM':
state.map((item) => {
if (item.id === action.id) {
return [
...state,
item.selected = true
];
} else {
return [
...state,
item.selected = false
];
}
});
default:
return state;
}
};
Here is my component which I want to react on every change of the redux store
import React from 'react';
import { connect } from 'react-redux';
import { removeItem, selectItem } from '../actions/items';
import { Badge, ListGroup, ListGroupItem, Button } from 'reactstrap';
const stylesForActiveItem = {
borderLeft: '4px solid red',
transition: 'all .5s',
marginLeft: '-4px',
borderRadius: '0'
}
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
const { removeItem, selectItem, id, title, selected } = this.props;
return (
<ListGroup className="Item">
<ListGroupItem
onClick={() => selectItem({ id })}
className={selected ? 'Item__text active-item' :
'Item__text'}
>{title} <Badge className="Item__badge">14</Badge>
</ListGroupItem>
<Button className="Item__btn" onClick={() => removeItem({ id
})}>Delete</Button>
</ListGroup>
);
}
}
const mapDispatchToProps = (dispatch) => ({
removeItem: (id) => dispatch(removeItem(id)),
selectItem: (id) => dispatch(selectItem(id))
})
export default connect(null, mapDispatchToProps)(Item);
state.map((item) => {
if (item.id === action.id) {
return [
...state,
item.selected = true
];
} else {
return [
...state,
item.selected = false
];
}
});
//I guess you need to do something like this
state.map((item) => {
if (item.id === action.id) {
return {...item, selected:true}
} else {
return {...item, selected:false}
}
});
Since even though map returns new array, internal object should also not get mutated. That is why we spread and create a new item object inside.
There is no need to create arrays again in map with entire state. That will just change your state structure instead of just changing a boolean.