Prevent redux from adding duplicate items to cart - javascript

I am using redux to put products in a cart for a react native project. At the moment it's functional, but I can add duplicate items. I want to prevent that.
What's the best way to modify the reducer that will stop storing duplicates?
My Reducer:
const cartItems = (state = [], action) => {
//action type are the constatns
switch (action.type) {
case ADD_TO_CART:
// TODO: dont add duplicates
return [...state, action.payload];
case REMOVE_TO_CART:
//filter through the single item that matches payload and remove it
return state.filter(cartItem => cartItem !== action.payload);
case CLEAR_TO_CART:
//return empty state which clears cart
return (state = []);
}
//return the state
return state;
};
My action:
export const addToCart = (payload) => {
return {
type: ADD_TO_CART,
payload,
}
}

Use find to check to see if an object with that product ID exists in state. If it does return the state otherwise return the updated state.
const { product_id } = action.payload;
const dupe = state.find(obj => obj.product_id === product_id);
return dupe ? state : [...state, action.payload ];

You can add some code before doing something like:
return {...state, cart: [...state.cart].push(payload)}
. for example:
const lookForCart = state?.cart?.find(crt => crt?.cardId === payload?.cardId)
if (lookForCart) return state
return {...state, cart: [...state.cart].push(payload)}

you must check duplicate first before call action add_cart
case 1: if not has exists => push in array redux store
case 2: if has item => consider change property example increase number quantity product

You should filter out the product if it is in the Store and add the new action.payload
This will ensure that payload quantity, price, total, quantity is updated
Code:
case ADD_TO_CART:
// TODO: dont add duplicates
return [...state.filter(p => p.id !== action.payload.product_id), action.payload];

Related

How to delete a specific item from localstorage in react redux

How can I remove a specific item (by id) from localstorage using react (redux - persist)? handleSubmit is working fine, but handleDelete, is not. I have this:
handleSubmit = event => {
event.preventDefault();
this.props.addWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
};
handleDelete = (event, id) => {
this.props.deleteWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
}
const mapStateToProps = state => ({
allWeather: state.allWeather
});
const mapDispatchToProps = dispatch =>
bindActionCreators(WeatherActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(WeatherList);
And button in form to call handleDelete:
<form onSubmit={this.handleDelete}><button type="submit" id="add" onClick={this.handleDelete}>Remove City</button></form>
My localstorage:
allWeather: "[{\"id\":0.5927975642362653,\"city\":\"Toronto\"},{\"id\":0.8124764603718682,\"city\":\"Fortaleza\"},{\"id\":0.9699736666575081,\"city\":\"Porto\"},{\"id\":0.852871998478355,\"city\":\"Tokio\"},{\"id\":0.8854642571682461,\"city\":\"New York\"}]"
My reducer:
export default function allWeather(state = [], action) {
switch (action.type) {
case "ADD_WEATHER":
return [...state, { id: Math.random(), city: action.payload.city }];
case "DELETE_ITEM":
return [...state, state.weatherCity.filter((event, id) => id !== action.payload.id)];
default:
return state;
}
}
And actions:
export const deleteWeather = id => ({
type: "DELETE_ITEM",
payload: { id }
});
I appreciate any help.
Your problem is that you are using the spread operator, which copies the content of the current state first. Then you are adding the items that were returned from the filter method. So you aren't deleting but adding. To delete from an array use the filter method only, without the spread operator like that:
return state.filter( (city) => city.id !== action.payload.id )
Also the state is an array, not an object, so this is invalid state.weatherCity.

What is the best way to calculate the total cart items in the Redux store?

while working on an e-commerce app I need to calculate the total items in the cart. Cart is saved in the redux store, and the count state is also there to count the items.
I have tried different solutions like:
1)Reducing the state.cart in the Cart Reducer. But this was not working fine as I tried to reduce the state.cart on every add or remove item from the state.cart And it is not taking the immediate state.cart value, hence not deleting the items correspondingly.As following
if(action.type === ADD_TO_CART) {
return {
...state,
cartItems: action.payload,
total : state.cartItems.reduce((acc,val) =>{
acc += val.count;
return acc
},0)
}
}
else if(action.type === REMOVE_FROM_CART){
return {
...state,
cartItems: action.payload,
total : state.cartItems.reduce((acc,val) =>{
acc += val.count;
return acc
},0)
}
}
2)Second I create an action creator getTotal() and dispatched it from the useEffect hook. It's working totally fine but the only problem which I think is major one, console is giving max depth call stack error.As
Action.js file
export const getTotal = () => dispatch =>{
return dispatch({type: GET_TOTAL})
}
reducer.js file
else if(action.type === GET_TOTAL){
return {
...state,
total: state.cartItems.reduce((acc,val) =>{
acc += val.count;
return acc
},0)
}
}
Navbar.js file
useEffect(() =>{
dispatch(getTotal())
})
// -------
<Link to="/cart">
<Badge badgeContent={total} color="primary">
<ShoppingCartOutlined />
</Badge>
</Link>
So What is the best way to calculate the total items in the cart or if somehow can I handle the useEffect hook to avoid the call stack error?
Looking forward :)
thanks,
Don't use dispatch for calculated values.
Instead, create a helper function for calculation logic:
const getCartTotal = ({cartItems}) => {
return cartItems.reduce((acc,val) =>{
acc += val.count;
return acc;
}
};
In component:
const cart = useSelector(state => state.cart);
const total = getCartTotal(cart);
<Link to="/cart">
<Badge badgeContent={total} color="primary">
<ShoppingCartOutlined />
</Badge>
</Link>
You can create selectors for cart and cart total:
import {createSelector} from 'reselect';
// if you're already using #reduxjs/toolkit:
// import {createSelector} from '#reduxjs/toolkit';
const cartSelector = (state) => state.cart;
const cartTotalSelector = createSelector(
cartSelector,
state => getCartTotal(state);
);
In component:
const total = useSelector(cartTotalSelector);
Let's take a look at your reducer first.
if(action.type === ADD_TO_CART) {
// Land on a new state
// Refine the new state
// Return it
}
}
You need to take your time to do the above three steps, the reason why it's not working is you did too fast :)
const newState = {
...state, cartItems.action.payload
}
newState.total = newState.cartItems.reduce((acc,val) =>{
acc += val.count;
return acc
},0)
return newState
}
Doesn't matter how you do it, focus on the newState not state. It does seem everything can be done in one step, but there's order of things you need to pay attention to.
As best practice the store shouldn't calculate anything but rather infer the next state from the current and combine them as needed via the action payload.
import { ADD_TO_CART, EMPTY_CART, REMOVE_FROM_CART } from '../actions/types';
const initialState = {
cart: [],
count: 0,
}
export default function(state=initialState, action) {
switch(action.type){
case ADD_TO_CART:
return {
...state,
cart: [action.payload, ...state.cart],
count: state.count + 1
}
case EMPTY_CART:
return {
...state,
cart: [],
count: 0
}
case REMOVE_FROM_CART:
return {
...state,
cart: state.cart.filter((item, i) => i !== action.payload.index),
count: state.count - 1
}
default:
return state
}
}

ngrx how to find object by string id and replace in array of objects

i dispatch object to payload with data ,in NGRX i need to find in array of objects by object id and if its exist replace whole object.
i tried with map , but its just adding the same one to array.
Object example :
datasetId: "9137"
id: "statedE1123-213-1411"
name: "sam"
queuePosition: 1
status: "QUEUED"
export const initialState: State = {
queueData: []
};
const queueReducer = createReducer(
initialState,
on(queueActions.updateQueueData, (state, action) => ({
...state,
queueData: [...state.queueData, action.payload]
})),
on(queueActions.updateQueueDataItem, (state, action) => ({
...state,
queueData: state.queueData.map(item => (item.id === action.payload.id) ? action.payload : item)
})),
);
export const updateQueueDataItem = createAction(
'[Queue] Update Queue Data',
props<{payload: IQueue}>(),
);
this.store.dispatch(updateQueueDataItem({payload: item}));
I would highly suggest taking a look into ngrx entity, since it offer a nice solution. Also storing your data in objects would make it easier (exactly what ngrx entity does under the hood), because you could directly reference it by id.
I put together a quick solution:
on(queueActions.updateQueueDataItem, (state, action) => {
const objectIndex = state.queueData.findIndex(item => (item.id === action.payload.id);
//Don't do anything
if(objectIndex === -1) return state;
//Update state immutably
const filtered =state.queueData.filter((item)=>item.id === action.payload.id);
return{
...state,
queueData: [...filtered,action.payload]
}
}),

What is the best to update state inside an array

I have a code that loops through all the orders an updates the is_confirmed property to 1. The thing is I have to loop through all the orders find the one that matches the order id and update it.
My question is there more efficient way to do this without looping through all the objects?
export const orders = (state = [], action) => {
const { type, payload } = action;
switch (type) {
case "NEW_ORDER":
const { new_order } = payload;
const new_state = state.concat(new_order);
//console.log(new_state);
return new_state;
case "CONFIRM_ORDER":
const { index } = payload;
return state.map((order) => {
if (order.id === index) {
return { ...order, is_confirmed: 1 };
} else {
return state;
}
});
}
return state;
};
First of all, it would be best if you make your state an object
export const orders = (state = {orders : []},action)
And access your array as state.orders.
Next, never mutate a state variable, make a copy of it first
let ordersCopy= [...state.orders]
Then you can alter this array and set it to state:
ordersCopy.forEach((order) => {
if(order.id === index){
ordersCopy.splice(index,1,{...order, is_confirmed: 1})
}
return {...state, orders: ordersCopy}
And in your other case NEW_ORDER:
return {...state, orders: [...state.orders, new_order]}
I would just make a copy of the array and find the index of the matched element using findIndex. Then, update it using brackets to access the element:
case "CONFIRM_ORDER":
const { index } = payload;
const ordersCopy = [...state]
const orderIndex = ordersCopy.findIndex(order => order.id === index)
ordersCopy[orderIndex].is_confirmed = 1
return ordersCopy
Create a state with React Class or Hook to useState()
Please check here- https://reactjs.org/docs/hooks-intro.html

Reactjs/Redux Edit functionality not working

I built an app and added CRUD functionality and everything works fine except the edit functionality. The problem is when I try to edit its actually hitting the right database and updates the entry but in the react app its just force updates all the entries to particular one entry.
Update Saga :-
function* updateFeedbackSaga(action) {
try {
const updateData = yield call(api.feedback.edit, action.payload);
yield put(actions.updateFeedback(updateData));
console.log(updateData);
} catch (err) {
yield put(actions.updateFeedbackErrors(err.response.data));
}
}
Edit Reducer
import * as actionTypes from "../Actions/types";
const initialState = {
feedbacks: [],
feedback: {},
loading: false
};
export default (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: state.feedbacks.map(
feedback =>
feedback.id === action.payload.id ? action.payload : feedback
)
};
default:
return state;
}
};
Actions
//Edit and update Feedback
export const updateFeedbackRequest = newFeedbackData => ({
type: actionTypes.UPDATE_FEEDBACK_REQUEST,
payload: newFeedbackData
});
export const updateFeedback = updatedData => ({
type: actionTypes.UPDATE_FEEDBACK,
payload: updatedData
});
export const updateFeedbackErrors = errors => ({
type: actionTypes.GET_ERRORS,
payload: errors
});
That's how printing
<section className = "feedback">
<div className = "employees__table" >
<h4 className = "semi-heading" > Feedback Table < /h4>
{
FeedbackList feedbacks = {feedbacks} />
}
</div>
</section >
const mapStateToProps = state => ({
feedbackList: selectors.FeedbackSelector(state)
});
HERE ARE THE IMAGES
This is my feedbacklist
If I edit the first item then state is like this
My feedbacklist is repeating edited feedback. I don't know where i am doing wrong.
Here is my database
Here is the working code:
https://codesandbox.io/s/github/montygoldy/employee-review/tree/master/client
login: montyjatt#gmail.com
password: 12345678
do you need to loop over all feedback when you already know the updated I'd?
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks[action.payload.id]: action.payload.body
};
This will only update a single item because the ID is part of the key.
The way you have it currently the feedbacks will all be replaced by the single value that matches the ID.
If you're planning on sending multiple id's then you'll want to use the spread operator.
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: {
...state.feedbacks,
...action.payload
}
};
In this case you're spreading out the old feedback items and then using the new payload with the spread operator to overwrite only the ones with matching id's.
Of course this means the action.payload should match your feedback structure.
Ok SO I have found the fix, actually, it's my id reference in the reducer was incorrect.
correct way is
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: state.feedbacks.map(
feedback =>
feedback._id === action.payload._id ? action.payload : feedback
)
};

Categories