I am trying to increase the number of items in my cart using Redux (toolkit) in React native.
I have created an add to basket reducer but upon clicking on the Plus button to increase the number of items, I get a line of the same item being duplicated below and I get a warning saying there are children with the same key.
BasketSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
items: [],
};
export const basketSlice = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, action) => {
state.items.push(action.payload);
},
removeFromBasket: (state, action) => {
const index = state.items.findIndex(
(basketItem) => basketItem.id === action.payload.id
);
let newBasket = [...state.items];
if (index >= 0) {
newBasket.splice(index, 1);
} else {
console.warn(
`Cant remove product (id: ${action.payload.id}) as its not in the basket!`
);
}
state.items = newBasket;
},
},
});
// Action creators are generated for each case reducer function
export const { addToBasket, removeFromBasket } =
basketSlice.actions;
export const selectBasketItems = (state) => state.basket.items;
export default basketSlice.reducer;
ProductDetailScreen is the part where I can add an item to the basket
import { View, Text, Image, TouchableOpacity } from "react-native";
import React from "react";
import { useRoute } from "@react-navigation/native";
import { Ionicons } from "@expo/vector-icons";
import BackButton from "../components/BackButton";
import { useDispatch } from "react-redux";
import { addToBasket } from "../../features/basketSlice";
import Toast from "react-native-toast-message";
const ProductDetailScreen = () => {
const route = useRoute();
const dispatch = useDispatch();
const { id, image, name, price } = route.params;
const addItemToCart = () => {
dispatch(addToBasket({ id, image, name, price }));
Toast.show({
type: "success",
text1: "Item added to cart!",
position: "bottom",
});
};
return (
<View className="bg-white flex-1 justify-between">
<Image
source={{ uri: image }}
className="h-auto w-full rounded-t-2xl flex-1 rounded-b-2xl"
resizeMode="cover"
/>
<BackButton />
<TouchableOpacity
onPress={addItemToCart}
className="bg-indigo-500 mx-3 rounded-md p-3 flex-row justify-between mt-5 mb-1"
>
<View className="flex-row space-x-1">
<Ionicons name="cart-sharp" size={30} color="white" />
<Text className="text-white font-normal text-lg">Add to cart</Text>
</View>
<Text className="text-white font-extralight text-lg">|</Text>
<Text className="text-white font-normal text-lg">${price}</Text>
</TouchableOpacity>
</View>
);
};
export default ProductDetailScreen;
And the CartScreen.js
const CartScreen = () => {
const dispatch = useDispatch();
const items = useSelector(selectBasketItems);
const handleDecrement = (id) => {
dispatch(removeFromBasket({ id }));
};
const handleIncrement = (item) => {
dispatch(
addToBasket({
...item,
quantity: item.length + 1,
})
);
};
const totalPrice = items.reduce((total, item) => total + item.price, 0);
return (
<SafeAreaView className="flex-1 bg-white">
{items.length === 0 ? (
<View className="flex-1 items-center justify-center">
<Text className="text-lg font-semibold">Your cart is empty</Text>
</View>
) : (
<View className="flex-1">
{items.map((item) => (
<View key={item.id} className="">
<View className="flex-row my-1 mx-3">
<View className="flex flex-row items-center">
<Image
source={{ uri: item.image }}
className="h-16 w-16 object-contain mr-2 rounded-md"
/>
<View>
<Text className="text-lg font-semibold">{item.name}</Text>
<Text className="text-gray-400 font-bold">
$ {item.price}
</Text>
</View>
</View>
<View className="flex-1 flex flex-row justify-end items-end">
<TouchableOpacity
disabled={!items.length}
onPress={() => handleDecrement(item.id)}
>
<AntDesign name="minuscircleo" size={30} color="black" />
</TouchableOpacity>
<Text className="px-2">{items.length}</Text>
<TouchableOpacity onPress={() => handleIncrement(item)}>
<AntDesign name="pluscircle" size={30} color="#757575" />
</TouchableOpacity>
</View>
</View>
</View>
))}
<View></View>
<View className="flex-row items-center justify-between px-6 py-5">
<Text className="text-lg font-semibold">Total:</Text>
<Text className="text-lg font-semibold">${totalPrice}</Text>
</View>
</View>
)}
</SafeAreaView>
);
};
2
Answers
I had to add more variables in the initial state of the slice and upon adding an element to the basket, create an instance that would hold the quantity and there update it when an item is added or removed while at the same time calculating the total price. BasketSlice.js
And I applied the according changes on my CartScreen and now this is what it looks like
CartScreen.js
The problem is how you wrote your reducer function in BasketSlice.js. Right now it pushes the payload item whenever you dispatch an addtoBasket action . Instead what it should ideally do is check if an item with id is already in cart , if yes it should increment the quantity of the item , if item not present in cart it should insert into the array with quantity as one
BasketSlice.js
Also change the how quantity is shown in CartScreen as follows
CartScreen.js