The state for cart is following;
State> Cart> Products
{
'0': {
_id: '63c6e3d5f73ff2b0604b4e9c',
name: 'Oriental Rubber Table',
price: 521,
category: 'Pizza',
image: 'https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
**amount: 1**
}
}
And I have this function to increase amount of the product. But It increases all the products in the basket not only the chosen item.
incrementAmount: (state, action) => {
return {products: state.products.map(product => {
if(product.id === action.payload){
return {...product, amount: product.amount + 1}
}else{
return product
}
})}
},
Related
I have two reducers ItemReducer where I am setting initially all the items which I am fetching using Axios calling GET API. Another reducer is CartReducer where I am storing cartItems.
ItemReducer
{
item_id: '1',
category_id: '12',
item_name: 'Churros',
item_image: 'item_081220170230.jpg',
is_variant: '2',
item_price: '9',
currency_code: 'USD',
currency_symbol: '$',
item_quantity: '1',
is_type: 'item'
}
CartReducer
{
item_id: '1',
category_id: '12',
item_name: 'Churros',
item_image: 'item_081220170230.jpg',
is_variant: '2',
item_price: '9',
currency_code: 'USD',
currency_symbol: '$',
item_quantity: '1',
is_type: 'item'
}
The problem is whenever I try to update current cart item's quantity it updates itemReducer item's quantity also I tried to use an index for catching exact item but not worked for me.
Here is the code.
const qtyChange = (item_id, variation_id) => {
if (cartReducer.items){
const {items} = cartReducer;
if ( item_id && !variation_id ) {
const filteredItem = items.filter(item => item.item_id === item_id).pop();
filteredItem.item_quantity = Number( filteredItem.item_quantity ) + 1;
} else{
const filteredItem = items.filter(item => item.item_id === item_id && item.variation_id === variation_id).pop();
filteredItem.item_quantity = Number ( filteredItem.item_quantity ) + 1;
}
}
};
I'm trying to filter an object from an array (redux reducer),
const data = [
{id: 0, name: 'Printed Men black Shirt', itemCode: 1000, price: 530, currency: '$', manufacturer: 'ColorWorld'},
{id: 1, name: 'Denim blue white shorts', itemCode: 1001, price: 230, currency: '$', manufacturer: 'ColorWorld'},
{id: 2, name: 'Solid Men blue pants', itemCode: 1003, price: 1530, currency: '$', manufacturer: 'Mecrono Hipli'},
{id: 3, name: 'Checkerd Men Blue Shorts', itemCode: 1300, price: 2530, currency: '$', manufacturer: 'Mecrono Hipli Mini'},
{id: 4, name: 'Self Designed Pant', itemCode: 1056, price: 130, currency: '$', manufacturer:
];
export const listProducts = (state = data, action) => {
switch(action.type){
case LIST_ALL_PRODUCTS: {
return state;
}
case SEARCH_PRODUCT: {
return state.filter((e)=> e.name.includes(action.payload))
}
default:
return state
}
}
This reducer always updates the actual state. Why this is not making a new filtered array?
As I mentioned in the comment, you can also separately store the filter itself and do the filtering not in the reducer, but in the component:
Example reducers:
export const listProducts = (state = data, action) => {
switch (action.type) {
default:
return state;
}
};
export const productNameFilter = (state = "", action) => {
switch (action.type) {
case SEARCH_PRODUCT: {
return action.payload.toLowerCase();
}
default:
return state;
}
};
// assuming root reducer is:
// combineReducers({ listProducts, productNameFilter })
In the productNameFilter reducer, the SEARCH_PRODUCT action's payload should contain the product name filter. Resetting the search filter is as simple as setting the state to ''.
Example component, using hooks:
const ProductList = () => {
const products = useSelector((state) => state.listProducts);
const productFilter = useSelector((state) => state.productNameFilter);
const shownProducts = products.filter((product) =>
product.name.toLowerCase().includes(productFilter.toLowerCase())
);
return (
<ul>
{shownProducts.map((p) => (
<p key={p.id}>{p.name}</p>
))}
</ul>
);
};
or with connect:
const ProductList = ({ products, productFilter }) => {
const shownProducts = products.filter((product) =>
product.name.toLowerCase().includes(productFilter)
);
return (
<ul>
{shownProducts.map((p) => (
<p key={p.id}>{p.name}</p>
))}
</ul>
);
};
export default connect(ProductList)((state) => ({
products: state.listProducts,
productFilter: state.productNameFilter,
}));
You can solve your issue by converting the state data structure to an object and maintain a separate property in redux state say filteredData.
Like this
const initialState = {
data: [
{
id: 0,
name: 'Printed Men black Shirt',
itemCode: 1000,
price: 530,
currency: '$',
manufacturer: 'ColorWorld',
},
{
id: 4,
name: 'Self Designed Pant',
itemCode: 1056,
price: 130,
currency: '$',
manufacturer: '',
},
],
filteredData: [],
}
export const listProducts = (state = initialState, action) => {
switch (action.type) {
case LIST_ALL_PRODUCTS: {
return state
}
case SEARCH_PRODUCT: {
return { ...state, filteredData: state.data.filter((e) => e.name.includes(action.payload)) }
}
default:
return state
}
}
Another option
Use single source of data in redux and do the filtering logic in the component itself. See sample demo here if it helps.
This is my reducer file in react
const initialState = {
products: [
{
name: 'Icecream',
inCart: false,
num: 2
},
{
name: 'Cake',
inCart: true,
num: 5
}
]
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case REVERSE:
let newProd = [ ...state.products ];
newProd.filter((a) => {
if (a.inCart === true) {
return a;
}
});
console.log(newProd);
return {
...state,
products: newProd
};
default:
return state;
}
};
and this is the console log of newProd which shows the filter function doesn't work
0: {name: "Icecream", inCart: false, num: 2}
1: {name: "Cake", inCart: true, num: 5}
length: 2
__proto__: Array(0)
How can I filter the products array so I can only get the item that has inCart = true and replace it with the old products array ?
Filter returns a new array & won't mutate the original one. You are filtering but not assigning the output of filter to a variable.
Do this
export const reducer = (state = initialState, action) => {
switch (action.type) {
case REVERSE:
let newProd = state.products.filter((a) => { //<---- like this
if (a.inCart === true) {
return a;
}
});
console.log(newProd);
return {
...state,
products: newProd
};
default:
return state;
}
};
You can also do it in a single liner - like this:
...
case REVERSE:
return {
...state,
products: state.products.filter(a => a.inCart)
};
...
Getting lost in a deeply nested setState function.
What I am trying to do is to add an object to an array within an object within my components state. I have succeeded in doing so but I am trying to make it so if that object already exists, the function will update the quantity. I'm not sure what I'm doing wrong.
state
this.state = {
products : [
{
productId: 1,
productImage: 'tee1',
productName: 'The Road Is Home Tee',
productPrice: 25
},
{
productId: 2,
productImage: 'shorts1',
productName: 'Striped Swim Shorts',
productPrice: 50
},
{
productId: 3,
productImage: 'tee2',
productName: 'Gray Long Sleeve',
productPrice: 100
},
{
productId: 4,
productImage: 'hat1',
productName: 'American Snapback',
productPrice: 25
},
{
productId: 5,
productImage: 'shorts2',
productName: 'American Shorts',
productPrice: 50
},
{
productId: 6,
productImage: 'hat2',
productName: 'Flex Fit Hat',
productPrice: 100
}
],
cartData : {
items: [],
total: 0
}
}
addToCart()
addToCart = (productId, size, quantity) => {
for( let i=0; i < this.state.cartData.items.length; i++ ) {
if (productId === this.state.cartData.items[i].productData.productId) {
this.setState(prevState => {
const items = prevState.cartData.items.map(item => {
if(i === (item.productId -1)) {
return item.productQuantity + quantity;
}
})
return {
items,
}
})
} else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
EDIT ***********
NEW CODE IS MORE ORGANIZED AND I HAVE IMPLEMENTED AN IF STATEMENT IN THE BEGINNING THAT CHECKS IF CART IS EMPTY BEFORE I LOOP OVER THE ITEMS. ALSO FIXED A COUPLE SMALL THINGS AND ADDED SOME COMMENTS BUT STILL NOT GETTING THE RESULTS I WOULD LIKE
addToCart = (productId, size, quantity) => {
// IF CART IS EMPTY (CANT USE FOR LOOP)
if ( this.state.cartData.items.length === 0 ) {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
// IF CART IS NOT EMPTY
else {
// LOOP THROUGH EACH ITEM
for( let i=0; i < this.state.cartData.items.length; i++ ) {
// IF PRODUCT EXISTS
if (productId === this.state.cartData.items[i].productData.productId) {
// SET STATE (RETURNS A FUNCTION)
this.setState(prevState => {
// MAP THROUGH EACH ITEM
const items = prevState.cartData.items.map(item => {
// IF THE PRODUCT IDS MATCH
if(i === (item.productData.productId -1)) {
// RETURN AN OBJECT WITH SAME ITEM PROPERTIES
// ADJUST QUANTITY
return {
...item,
productQuantity: item.productQuantity + quantity,
}
}
// RETURN UNAFFECTED ITEMS AS WELL
else {
return item
}
})
// RETURN OUR ITEMS TO SET STATE
return {
items,
}
})
}
// IF PRODUCT DOES NOT EXIST
else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
}
AS OF NOW MY APPLICATION WILL ADD THE FIRST PRODUCT CORRECTLY. THEN ANYTHING AFTER THAT I START GETTING PROBLEMS. WHEN I TRY TO ADD ANOTHER PRODUCT WITH THE SAME PRODUCT ID IT WILL NOT JUST INCREMENT THE QUANTITY IT CREATES A WHOLE OTHER OBJECT.
sooo lost here please help!!!
p.s.
getting errors that i am not getting with localhost inside of sandbox so not really an option here
I didn't try running the code (will do if you provide a codesandbox or something), but that should help.
addToCart = (productId, size, quantity) => {
for( let i=0; i < this.state.cartData.items.length; i++ ) {
if (productId === this.state.cartData.items[i].productData.productId) {
this.setState(prevState => {
const items = prevState.cartData.items.map(item => {
if(i === (item.productId -1)) {
// returns a copy of the known item with updated quantity
return {
...item,
productQuantity: item.productQuantity + quantity,
};
} else {
return item // you need to return items that are not modified too
}
})
return {
items,
}
})
} else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
It works. I hope it helps you. (some changes for a more organized code):
const products = [
{
productId: 1,
productImage: 'tee1',
productName: 'The Road Is Home Tee',
productPrice: 25
},
{
productId: 2,
productImage: 'shorts1',
productName: 'Striped Swim Shorts',
productPrice: 50
},
{
productId: 3,
productImage: 'tee2',
productName: 'Gray Long Sleeve',
productPrice: 100
},
{
productId: 4,
productImage: 'hat1',
productName: 'American Snapback',
productPrice: 25
},
{
productId: 5,
productImage: 'shorts2',
productName: 'American Shorts',
productPrice: 50
},
{
productId: 6,
productImage: 'hat2',
productName: 'Flex Fit Hat',
productPrice: 100
}
]
const [cart, setCart] = useState({
items: [],
total: 0
})
const isProductExist= productId =>
cart.items.find(product => product.productId == productId)
addToCart = (productId, size, quantity) => {
if (isProductExist(productId)) {
setCart(prevState => ({
...prevState,
items: cart.items.map(item => {
if (item.productId == productId) item.productQuantity += quantity
item.productQuantity
return item
})
}))
} else {
setCart(prevState => ({
...prevState,
items: [
...prevState.items,
{
productId: products[productId - 1].productId,
productSize: size,
productQuantity: quantity
}
]
}))
}
}
In my reducer called 'reducer_product_list', I have this array :
let initialState = [
{name: 'Article 1', price: 5, quantity: 10},
{name: 'Article 2', price: 15, quantity: 8},
{name: 'Article 3', price: 7, quantity: 15},
{name: 'Article 4', price: 9, quantity: 5},
{name: 'Article 5', price: 11, quantity: 100},
{name: 'Article 6', price: 23, quantity: 20},
]
When I get the action 'ADD_TO_CART', I want to decrease the quantity of the selected object. The payload is one of those objects.
I typed the code above :
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
initialState.map((product, i) => {
if (product.name === action.payload.name) {
initialState[i].quantity -= 1
return state;
}
});
default: return state
}
}
If I console.log my initialState, the quantity is decreasing, but in my container that renders the view, the quantity stays the same.
Thank you for your help.
Try this:
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return state.map(product => {
if (product.name === action.payload.name) {
return {...product, quantity: product.quantity-1}
};
return product;
});
default: return state
}
}
The reason is you have to return a new state object derived from the current state reflecting the desired changes according to the requested action. See Redux Reducers for more details.
Thank you for you help, it works well!
I just had to change one thing: instead of mapping on the initialState, I mapped on the state.
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return state.map(product => {
if (product.name === action.payload.name) {
return {...product, quantity: product.quantity - 1}
};
console.log(state)
return product;
});
default: return state
}
}