I am trying to use RTK query with different params in this situation:
I have shopping cart with mutliple items, each item has counter to control the quantity of the purchased item so i can add or remove (increase or decrease the number).
the problem:
while all counters have the same RTK query definition they share the mutation trigger and isLoading property so when click on one button the isLoading property switch to true and show spinners on all other counters buttons
cart component code:
const Cart = () => {
const {
isLoading,
isSuccess,
data: cart,
} = useGetCartQuery({ userId: userData.id });
const [addItem, { isLoading: isAddUpdating }] = useAddCartMutation();
const [removeItem, { isLoading: isRemoveUpdating }] = useRemoveCartMutation();
const handleAddBtn = (item: cartItemType) => {
if (item.product.availability > item.quantity) {
addItem({
userId: userData.id as string,
productId: item.product.id as string,
quantity: 1,
});
} else {
throw new Error("can't add more than items in stack");
}
};
const handleRemoveBtn = (item: cartItemType, count) => {
console.log(item, count);
if (item.quantity >= count) {
removeItem({
userId: userData.id as string,
productId: item.product.id as string,
count: count,
});
}
};
return (
<div>
<QCounter
availability={item.product.availability}
count={item.quantity}
handleAddBtn={() => handleAddBtn(item)}
handleRemoveBtn={() => handleRemoveBtn(item, 1)}
AddBtnLoading={isAddUpdating}
RemoveBtnLoading={isRemoveUpdating}
height="36px"
width="36px"
/>
</div>
);
};
QCounter code:
const QCounter = ({
availability,
count,
handleAddBtn,
handleRemoveBtn,
AddBtnLoading,
RemoveBtnLoading,
height,
width,
}) => {
return (
<div>
<button
onClick={handleRemoveBtn}
disabled={count == 1 || RemoveBtnLoading}
>
{!RemoveBtnLoading ? (
"-"
) : (
<FaSpinner className="animate-spin text-secondary" />
)}
</button>
<div>
<h3>{count}</h3>
</div>
<button
onClick={handleAddBtn}
disabled={count == availability || AddBtnLoading}
>
{!AddBtnLoading ? (
"+"
) : (
<FaSpinner className="animate-spin text-secondary" />
)}
</button>
</div>
);
};
solutions I tried:
1- define RTK Query function for each item using map function
i tried to define them in return with map function like this
{cart.items.map((item: cartItemType) => {
const [addItem, { isLoading: isAddUpdating }] = useAddCartMutation();
const [removeItem, { isLoading: isRemoveUpdating }] = useRemoveCartMutation();
then pass addItem and isLoading to each Qcounter but I got TS error :
React Hooks Must Be Called In a React Function Component or a Custom React Hook Function.
If I'm reading and understanding the post and code you are likely mapping the cart
data returned from the useGetCartQuery
to QCounter
in the first snippet, and want each QCounter
to have its own adding/removing "state" and indicator.
You can't call the mutation React hooks in loops or nested callbacks as this breaks the Rules of Hooks. I suggest moving the mutation hooks and add/remove logic into the QCounter
component so each cart item has its own state and logic.
Example:
Cart:
const Cart = () => {
...
const {
isLoading,
isSuccess,
data: cart,
} = useGetCartQuery({ userId: userData.id });
return (
<div>
{cart.items.map((item: cartItemType) => (
<QCounter
key={item.product.id}
item={item}
userId={userData.id}
height="36px"
width="36px"
/>
))}
</div>
);
};
QCounter:
interface QCounterProps {
item: cartItemType;
userId: string;
height: string;
width: string;
}
const QCounter = ({
height,
item,
userId,
width,
}: QCounterProps) => {
const [
addItem,
{ isLoading: isAddUpdating }
] = useAddCartMutation();
const [
removeItem,
{ isLoading: isRemoveUpdating }
] = useRemoveCartMutation();
const handleAddBtn = () => {
if (item.product.availability > item.quantity) {
addItem({
userId,
productId: item.product.id as string,
quantity: 1,
});
} else {
throw new Error("can't add more than items in stack");
}
};
const handleRemoveBtn = () => {
if (item.quantity > 0) {
removeItem({
userId,
productId: item.product.id as string,
count: item.quantity,
});
}
};
return (
<div>
<button
onClick={handleRemoveBtn}
disabled={item.quantity === 1 || isRemoveUpdating}
>
{isRemoveUpdating
? <FaSpinner className="animate-spin text-secondary" />
: "-"
}
</button>
<div>
<h3>{item.quantity}</h3>
</div>
<button
onClick={handleAddBtn}
disabled={item.quantity === item.product.availability || isAddUpdating}
>
{isAddUpdating
? <FaSpinner className="animate-spin text-secondary" />
: "+"
}
</button>
</div>
);
};