1

I'm developing an online store using React. I have a functionality where, upon clicking a button, a product is added to favorites. After this action, I want to update the profile request (which returns user and favorites).

My code:

// useProfile.ts

import UserService from "@/services/user.service";
import { useQuery } from "@tanstack/react-query";
import { useUser } from "./useUser";

export const useProfile = () => {
  const { user } = useUser();

  const { data } = useQuery({
    queryKey: ['profile'],
    queryFn: () => {
      return UserService.getProfile(user?.id || "");
    },
    select: ({ data }) => data
  });

  return {profile: data};
};
// LikeButton.tsx

import { useRef } from 'react';
import classes from './LikeButton.module.scss';
// @ts-ignore
import { ReactComponent as IconHeart } from '@/assets/img/svg/icon-heart.svg';
import { useProfile } from '@/hooks/useProfile';
import UserService from '@/services/user.service';
import { useMutation, useQueryClient } from '@tanstack/react-query';

interface ILikeButtonProps {
  productId: number;
}

function LikeButton({ productId }: ILikeButtonProps) {
  const { profile } = useProfile();
  const { invalidateQueries } = useQueryClient();

  const { mutate } = useMutation({
    mutationKey: ['toggle favourite'],
    mutationFn: () => UserService.toggleFavourite(productId),
    onSuccess: () => {
      console.log('onSuccess');
      invalidateQueries({ queryKey: ['profile'] });
    },
  });

  const btnRef = useRef<HTMLButtonElement>(null);

  const buttonClasses = profile?.favourites.some((fp) => fp.id === productId)
    ? `${classes.btn_like} ${classes['btn_like--active']}`
    : classes.btn_like;

  return (
    <button
      ref={btnRef}
      className={buttonClasses}
      onClick={() => mutate()}
      data-product-id={productId}
    >
      <IconHeart />
    </button>
  );
}

export default LikeButton;

However, for some reason, invalidateQueries doesn't seem to work, or perhaps I'm doing something incorrectly... I couldn't find an answer online :(

I added console.log - it works perfectly. But invalidateQueries isn't being triggered.

2
  • Did you inspect the network tab? After invalidating does network request triggers for profile? Commented Dec 31, 2023 at 11:35
  • No, the request didn't execute at all. You may check my answer Commented Dec 31, 2023 at 11:40

2 Answers 2

2

As I've figured out in a reproduction you cannot destructure from useQueryClient hook. This means the functions (like invalidateQueries) need to be bound to the QueryClient when executed.

This will work:

  // cannot destructure invalidateQueries
  const queryClient = useQueryClient();

  const { mutate } = useMutation({
    mutationKey: ['toggle favourite'],
    mutationFn: () => UserService.toggleFavourite(productId),
    onSuccess: () => {
      console.log('onSuccess');
      queryClient.invalidateQueries({ queryKey: ['profile'] });
    },
  });

Destructuring DOES NOT work:

  // this won't work
  const { invalidateQueries } = useQueryClient();

And to confirm, when reading the source code, the query client does indeed use this - https://github.com/TanStack/query/blob/ca6ad31c19c4d0dcfb8b95c462a575bc79c73bae/packages/query-core/src/queryClient.ts#L54.

If you destructure the invalidateQueries method and then call it, it's being called without a context, so it doesn't know how to use this correctly - https://github.com/TanStack/query/blob/main/packages/query-core/src/queryClient.ts#L250-L269

Sign up to request clarification or add additional context in comments.

Comments

0

The first issue was also that I wasn't returning anything from onSuccess.

Classic scenario: while waiting for an answer to my question, I found a solution myself. STRANGELY, the code snippet:

const {invalidateQueries} = useQueryClient();

const { mutate } = useMutation({
  mutationKey: ['toggle favourite'],
  mutationFn: () => UserService.toggleFavourite(productId),
  onSuccess: () => invalidateQueries({ queryKey: ['profile'] }),
});

isn't working, but an almost identical one is working perfectly:

const queryClient = useQueryClient();

const { mutate } = useMutation({
  mutationKey: ['toggle favourite'],
  mutationFn: () => UserService.toggleFavourite(productId),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['profile'] }),
});

Maybe someone in the comments will explain how this works...

P.S. Tnx to this issue

3 Comments

The linked issue is a problem because the user wasn’t returning anything from their mutationFn, it didn’t have anything to do with not returning from onSuccess. It’s not clear how this code is functionally different from the code in your original question and would solve your issue. I’ve used react query before tabstack query and have used the pattern of destructuring invalidate queries from the query client and it’s worked before, I’d be surprised in tanstack changes this functionality. I have a suspicion something else was going wrong, but I’ll try and get a repro going
Before this, I also wasn't returning anything from my onSuccess function. But it's very strange that the destructuring of useQueryClient broke everything.
Reproduction - interestingly you cannot destructure invalidateQueries when using useQueryClient which means it needs to be bound to this - it has nothing to do with returning from onSuccess - you just can't do this: const { invalidateQueries } = useQueryClient - you must use the query client without destructuring - const queryClient = useQueryClient

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.