How to prevent duplicate items in redux store? - reactjs

I am using Redux-toolkit. I am trying to prevent duplicate items in my store and also want to if any user tries to add the same item again. This time I want to simple update her quantity.
Here is my code:
import { createSlice, current } from "#reduxjs/toolkit";
const initialState = {
product: [],
};
export const cartSlice = createSlice({
name: "cart",
initialState: initialState,
reducers: {
addproduct: (state, action) => {
console.log(action.payload._id) // here _id is ok
const exist = state.product.find((pro) => pro._id === action.payload._id)
console.log(exist) //output undefined
state.product = [...state.product, action.payload];
},
},
})
export const { addproduct } = cartSlice.actions;
export default cartSlice.reducer;
My JSON file:
[
{
"_id": "62ad4c398bc6d37767e44423",
"name": "singu pizza",
"size": "small",
"category": "neapolitan",
"price": 250,
"image": "https://i.ibb.co/zVbq909/pizza.png"
},
{
"_id": "62ad4c398bc6d37767e44424",
"name": "singu pizza",
"size": "large",
"category": "neapolitan",
"price": 250,
"image": "https://i.ibb.co/zVbq909/pizza.png"
}
]

Assuming that there is a quantity property on your product:
addproduct: (state, action) => {
const item = state.product.find((pro) => pro._id === action.payload._id)
if (item) {
item.quantity++
} else {
state.product.push(action.payload)
}
},

If you are using sanity in your project follow the code below, it will update your quantity instead of adding the same product again in the cart, if you are not using sanity have a look at the code below I am sure it will give you some ideas to solve your problem.
Update your cartSlice with the code below :
const initialState = {
product: [],
};
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addproduct: (state, { payload }) => {
let newItem = payload;
const existItem = state.products.find((item) => item._id === payload._id);
state.products = existItem ? state.products.map((item) => item._id === existItem._id ? newItem : item) : [...state.products, newItem]
}
},
})
In the the page you want to use the reducer in add this:
const { products } = useSelector(store => store.cart)
const addToCart = () => {
let cartItem = products.find(item => item._id === product._id)
let quantity = cartItem ? cartItem.quantity + 1 : 1;
dispatch(() => addproduct({
_id: product._id,
name: product.name,
size: product.size,
category: product.slug.current,
price: product.price,
image: urlFor(product.image),
quantity
}))
}
addToCart is an onClick function, you do not need to add quantity property to your json file or the api it will be added when the product is selected to be in cart. The quantity above it will check if the item does not exist in the cart the quantity will be 1 and it will be added to the object of the product item, If the item does exist in the cart it will update the quantity by one.
Now everything should work just fine. If you had an error please feel free to message me, I would be more than happy to help you

Related

How to add one product to the cart when adding the same product

I have a shopping cart application where items can be added or removed. Implemented with Redux Toolkit
The problem is that I have two identical products with the same id added to my cart when you click on the "Add" button and there are two cards with the same product in the cart, and I need to have one card, but their number increased.How can I search for matches by id and, depending on the match, increase the quantity or add a new product to the cart. Tried to implement with forEach but then nothing works
const initialState = {
items: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
addItemToCart: (state, action) => {
state.items.forEach((item) => {
if(item.id === action.payload.id) {
state.totalQuantity += 1;
return state.items
} else {
state.items.push(action.payload);
state.totalQuantity += 1;
}
})
},
removeItemFromCart: (state) => {
state.totalQuantity -= 1;
},
},
});
I recently made a proyect related to a cart and I did somethings a little bit different, the initialState has to be an empty array and inside de reduceds we should apply de logic of the cart and quantity variable and the find method with spreed operator to write something like this:
const initialState = [];
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
addItemToCart: (state, {payload}) => {
const {id} = payload;
const doesItemExist = state.find((item) => item.id === id);
if(doesItemExist){
return state.map((item) => {
if(item.id === id){
return {
...item,
quantity: item.quantity + 1
}
}
return item;
})
} else {
state.push({
...payload, quantity: 1
})
}
},
The remove reducer takes part of the logic from thew add reducer but decremeting the quantity.
removeItemFromCart: (state, {payload}) => {
return state.map((item) => {
if(item.id === id){
return {
...item,
quantity: item.quantity - 1
}
}
return item;
})
},
},
});

How to store and get Cart Items to localstorage with Redux?

I'm using Redux toolkit and adding product items to the cart using redux in my eCommerce app, products are adding to the cart perfectly also I tried to store the added products to the localstorage which is also working fine but the thing is How can I use those items from localstorage to display the <CartItems> even after refreshing the page.
Here is my reducer:-
const productItemsSlice = createSlice({
name: "product",
initialState: {
items: [],
totalQuantity: 0,
localStorageItems: [],
},
reducers: {
addProduct(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
state.totalQuantity++;
if (!existingItem) {
state.items.push({
id: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.name,
image: newItem.image,
category: newItem.category,
});
} else {
existingItem.quantity = existingItem.quantity + 1;
existingItem.totalPrice = existingItem.totalPrice + newItem.price;
}
// LOCAL STORAGE
localStorage.setItem("list", JSON.stringify(state.items));
state.localStorageItems = JSON.parse(localStorage.getItem("list"));
},
},
});
And here I'm trying to access those setted cartItems to the localstorage:-
//<CartItems />
//Here state.productItem is from configureStore reducer object:-
// const store = configureStore({
// reducer: {
// productItem: productItemsSlice.reducer,
// }
// })
const productItems = useSelector((state) => state.productItem.localStorageItems);
const items = productItems.map((el) => {
return (
<CartItems
key={el.id}
item={{
id: el.id,
name: el.name,
image: el.image,
price: el.price,
category: el.category,
totalPrice: el.totalPrice,
quantity: el.quantity,
}}
/>
);
});
Please suggest me how can I achieve this, also if I remove items from cart.
Remove localStorageItems from initialState and add totalQuantity key for number of quantity to calculate how many items are added. Now we have to set localStorage for all these 3 initialState with different token key.
Outside of slice function we parse Items:-
const items =
localStorage.getItem("cartList") !== null
? JSON.parse(localStorage.getItem("cartList"))
: [];
const totalAmount =
localStorage.getItem("cartTotal") !== null
? JSON.parse(localStorage.getItem("cartTotal"))
: 0;
const totalQuantity =
localStorage.getItem("cartQuantity") !== null
? JSON.parse(localStorage.getItem("cartQuantity"))
: 0;
// adding this function to prevent repear code
const setCartListFunc = (items, totalAmount, totalQuantity) => {
localStorage.setItem("cartList", JSON.stringify(items));
localStorage.setItem("cartTotal", JSON.stringify(totalAmount));
localStorage.setItem("cartQuantity", JSON.stringify(totalQuantity));
};
This would be the initialState:-
initialState: {
items: items,
totalQuantity: totalQuantity,
totalAmount: totalAmount,
},
Now addProduct reducer should be like this to store the data to localStorage:-
addProduct(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
state.totalQuantity++;
if (!existingItem) {
state.items.push({
id: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.name,
image: newItem.image,
category: newItem.category,
});
} else {
existingItem.quantity = existingItem.quantity + 1;
existingItem.totalPrice = existingItem.totalPrice + newItem.price;
}
// added totalAmount to calculate number of items
state.totalAmount = state.items.reduce(
(total, items) => total + Number(items.price) * Number(items.quantity),
0
);
// Using function for all initialState
setCartListFunc(
state.items.map((item) => item),
state.totalAmount,
state.totalQuantity
);
},

Redux/Toolkits, useSelector Doesn't Work, Why?

I want to save my data in localstorage to evade the loss of it when reloading the page but i also need it in my gloable state to show a preview of it once it's added and never be lost when reloading the page,This is my slice format:
import { createSlice } from "#reduxjs/toolkit";
export const resumeSlicer = createSlice({
name: "resume",
initialState: {
Education: [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
],
},
reducers: {
SaveEducation: (state, action) => {
let Education = JSON.parse(localStorage.getItem("Education"));
if (!Education) {
Education.push(action.payload);
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
} else {
Education.push(action.payload);
let i = 0;
Education.map((e) => {
e.key = i;
i++;
return e.key;
});
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
}
},
getEducation: (state, action) => {
const items = JSON.parse(localStorage.getItem("Education"));
const empty_array = [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
];
state.Education.splice(0, state.Education.length);
state.Education = items;
},
},
});
And this is how i fetched:
const EdList = useSelector((state) => state.Education);
When i console.log it the result is "undefined"
Image Preview
https://i.stack.imgur.com/hD8bx.png
I'm hazarding a guess that the issue is a missing reference into the state. The chunk of state will typically nest under the name you give the slice, "resume" in this case. This occurs when you combine the slice reducers when creating the state object for the store.
Try:
const EdList = useSelector((state) => state.resume.Education);
If it turns out this isn't the case then we'll need to see how you create/configure the store and how you combine your reducers.

redux-toolkit sharing state between reducer

I building small budget calculator and its the first time i am using redux-toolkit, the problem is
How can share/pass state between reducers in redux-toolkit ? (how can use the totalIncomes and totalExpenses in the balance slice to calculate the total balance ?
another question is is ok to use redux-toolkit instead of plain redux
incomes.js :
const incomesSlice = createSlice({
name: "incomes",
initialState: {
list: [],
loading: false,
totalIncomes: 0,
lastFetch: null,
},
reducers: {
ADD_INCOME: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_INCOME: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
TOTAL_INCOMES: (state, action) => {
state.totalIncomes = state.list.reduce(
(acc, curr) => acc + curr.amount,
0
);
},
},
});
expenses.js :
const expensesSlice = createSlice({
name: "expenses",
initialState: {
list: [],
loading: false,
totalExpenses: 0,
lastFetch: null,
},
reducers: {
ADD_EXPENSE: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_EXPENSE: (state, action) => {
const index = state.list.findIndex(
(expense) => expense.id === action.payload.id
);
state.list.splice(index, 1);
},
TOTAL_EXPENSES: (state, action) => {
state.totalExpenses = state.list.reduce(
(acc, curr) => acc + curr.amount,
0
);
},
},
});
export const {
ADD_EXPENSE,
REMOVE_EXPENSE,
TOTAL_EXPENSES,
} = expensesSlice.actions;
export default expensesSlice.reducer;
balance.js :
const balanceSlice = createSlice({
name: "balance",
initialState: {
total: 0
},
reducers: {
CALC_TOTAL: (state, action) => {
// How to Calculate this ?
},
},
});enter code here
export const { CALC_TOTAL } = balanceSlice.actions;
export default balanceSlice.reducer;
For anyone looking into this - author's is the wrong approach to using redux for state management.
When using redux you want your state as normalized as possible - you shouldn't store uneeded/duplicated state or state that can be calculated based on other state, in this example there's no need to save totalIncomes since we can calculate this based on the list of incomes (same goes for totalExpenses and balance).
As mentioned, the totalIncomes shouldn't be part of the state but should be a calculated value, you can either calculate it on the fly or use a selector. In the below example I'll use a selector.
Redux Toolkit solution
To use it with Redux toolkit it might look something like this, I've removed parts of code for brewity:
incomes slice
// ...
const incomesSlice = createSlice({
name: "incomes",
initialState: {
list: [],
},
reducers: {
ADD_INCOME: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_INCOME: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
},
});
export const getTotalIncome = createSelector(
totalIncomeSelector,
calculateTotalIncome,
);
export function totalIncomeSelector(state) {
return state.incomes.list;
}
export function calculateTotalIncome(incomesList) {
return incomesList.reduce((total, income) => total + income.amount);
}
export const {
ADD_INVOICE,
REMOVE_INVOICE,
} = incomesSlice.actions;
export default incomesSlice.reducer;
expenses slice - removed parts for brewity
// ...
const expensesSlice = createSlice({
name: "expenses",
initialState: {
list: [],
},
reducers: {
ADD_EXPENSE: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_EXPENSE: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
},
});
export const getTotalExpense = createSelector(
totalExpenseSelector,
calculateTotalExpense,
);
export function totalExpenseSelector(state) {
return state.expenses.list;
}
export function calculateTotalExpenseexpenseList) {
return expensesList.reduce((total, expense) => total + expense.amount);
}
export const {
ADD_EXPENSE,
REMOVE_EXPENSE,
} = expensesSlice.actions;
export default expensesSlice.reducer;
balance slice - you don't really need a slice here, you just need a selector
import { getTotalIncome, totalIncomeSelector } from './incomeSlice';
import { getTotalExpense, totalExpenseSelector } from './expenseSlice';
export const getBalance = createSelector(
getTotalIncome,
getTotalExpense,
(totalIncome, totalExpense) => totalIncome - totalIncome,
);
Example component
// ...
function BalanceComponent({
totalIncome,
totalExpense,
balance,
}) {
return (
<div>
<h1>Finance overview</h1>
<div>
<span>Total Income:</span>
<span>{totalIncome}</span>
</div>
<div>
<span>Total Expense:</span>
<span>{totalExpense}</span>
</div>
<div>
<span>Balance:</span>
<span>{balance}</span>
</div>
</div>
);
}
function mapStateToProps(state) {
return {
totalIncome: getTotalIncome(state),
totalExpense: getTotalExpense(state),
balance: getBalance(state),
}
}
export default connect(mapStateToProps)(BalanceComponent);
Note: In the question the author seems to be breaking up his state into too many slices, all this can be a lot simpler by having it all as a single slice. That's what I would do.
Is it ok to use redux-toolkit instead of plain redux
YES. It was originally created to help address common concerns about Redux. See its purpose.
How can share/pass state between reducers in redux-toolkit?
You can pass the used state parts to action.payload.
dispatch(CALC_TOTAL(totalIncomes,totalExpenses))
You can use extraReducers and "listen" to to your incomes/expenses changes.
You can create a middleware or use createAsyncThunk where you can reference the most updated state with getState().
Toolkit docs.

Filter products depend on another ACTION in React-native Redux

I have an app which get all categories and products from the server with Redux ACTIONS. I need to filter products with a category Id. after load data action is complete, i call another action to filter products but i'm a little bit confused.
There is codes of few parts of the app:
ProductsActions:
export const GET_INITIAL_PRODUCTS_DATA = "GET_INITIAL_PRODUCTS_DATA";
export const GET_INITIAL_PRODUCTS_DATA_RESULT = "GET_INITIAL_PRODUCTS_DATA_RESULT";
export const GET_INITIAL_PRODUCTS_DATA_ERROR = "GET_INITIAL_PRODUCTS_DATA_ERROR";
export const FILTER_PRODUCTS_BY_CATEGORY_ID = "FILTER_PRODUCTS_BY_CATEGORY_ID";
export const getInitialProductsData = () => ({
type: GET_INITIAL_PRODUCTS_DATA
});
export const filterProductsByCategoryId = categoryId => ({
type: FILTER_PRODUCTS_BY_CATEGORY_ID,
categoryId
});
ProductsReducers:
import {
GET_INITIAL_PRODUCTS_DATA,
GET_INITIAL_PRODUCTS_DATA_RESULT,
GET_INITIAL_PRODUCTS_DATA_ERROR,
FILTER_PRODUCTS_BY_CATEGORY_ID
} from "../actions/products";
const initialState = {
isFetching: false,
data: {},
error: null
};
const filterProductsByCategoryId = (state, action) => {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_INITIAL_PRODUCTS_DATA:
return {
...state,
isFetching: true
};
case GET_INITIAL_PRODUCTS_DATA_RESULT:
return {
...state,
isFetching: false,
data: action.result
};
case GET_INITIAL_PRODUCTS_DATA_ERROR:
return {
...state,
isFetching: false,
error: action.error
};
case FILTER_PRODUCTS_BY_CATEGORY_ID:
return {
...state,
data: filterProductsByCategoryId(state, action.categoryId)
};
default:
return state;
}
};
export default reducer;
And there is my code to call filter action:
filterProducts = (title = "A") => {
const _categories = Object.values(this.props.categories);
const selectedCategory = _categories.find(
category => category.title === title
);
this.props.dispatch(filterProductsByCategoryId(selectedCategory.id));
My questions is:
A) Is there is a way to filter my data and display them in UI and refresh them without using ACTIONS way??
B) If A's answer is No!, How can i get my state.data and filter them in FILTER_PRODUCTS_BY_CATEGORY_ID?
Thanks.
You can use the Array.prototype.filter() to return filtered result.
keep in mind that this will return an array and not a single value, which is a good thing if you are using this filter within your reducer. because your reducer's shape is an array and not an object.
Running example:
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, action) => {
return state.filter(c => c.id === action.categoryId);
};
const result = filterProductsByCategoryId(myData, {categoryId: 2});
console.log(result);
I think it is more appropriate to create a selector for a singular product that will handle this kind of action, this way you will be able to return an object instead of an array with one product in it.
Not to mention the benefits of using reselect to do some memoizations.
For this task you can use the Array.prototype.find():
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, id) => {
return state.find(c => c.id === id);
};
const result = filterProductsByCategoryId(myData, 2);
console.log(result);

Resources