Search code examples
reactjsreduxredux-toolkitrtk-query

How to use the same RTK query function multiple times with different params in the same component?


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.


Solution

  • 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>
      );
    };