T
TanStack•3y ago
itchy-amethyst

Is this weird rollback due to an unwanted re-fetch? (optimistic updates)

Check this code and this exact line: https://github.com/NarniaT/favorites-react/blob/8c2b2560c287fe7d14eecb13cd485055cad875f0/src/fake-fetchers/FavoritesFetcher.ts#L8 If you change the timeout in the code above to a value less than 500 (which is the time it takes for the toggleFavorite function in the same file to resolve), like 300, then checking an item as favorite will check it instantly (optimistic), but then for a short while it rolls back to its unchecked state, and then once again when the query has been resolved, it gets checked. I've also a GIF of what is happening. After searching a bit about this, I found this Q&A on RQ's GitHub discussion: https://github.com/TanStack/query/discussions/2245#discussioncomment-704718 I thought my issue might be that, so I tried the solution suggested by TkDodo, but it didn't work too. Here's the way I tried their solution:
// useFavorites.ts
export default function useFavorites() {
const queryClient = useQueryClient();
// 👇 THIS
const isMutating = useIsMutating({ mutationKey: ["favorites"] });
const {
data: favorites,
isLoading,
isError: isRequestError,
} = useQuery<Product["id"][], Error>(["favorites"], getFavorites, {
enabled: isMutating === 0,
});
// ...
const { mutate: _toggleFavorite, isError: isMutationError } = useMutation(
__toggleFavorite,
{
mutationKey: ["favorites"],
onMutate: ({ id }) => {
toggleFavInCache(id);
},
onSettled: () => {
queryClient.invalidateQueries(["favorites"]);
},
onError: (error, { id }) => {
toggleFavInCache(id);
...
// useFavorites.ts
export default function useFavorites() {
const queryClient = useQueryClient();
// 👇 THIS
const isMutating = useIsMutating({ mutationKey: ["favorites"] });
const {
data: favorites,
isLoading,
isError: isRequestError,
} = useQuery<Product["id"][], Error>(["favorites"], getFavorites, {
enabled: isMutating === 0,
});
// ...
const { mutate: _toggleFavorite, isError: isMutationError } = useMutation(
__toggleFavorite,
{
mutationKey: ["favorites"],
onMutate: ({ id }) => {
toggleFavInCache(id);
},
onSettled: () => {
queryClient.invalidateQueries(["favorites"]);
},
onError: (error, { id }) => {
toggleFavInCache(id);
...
So why is this happening?
No description
6 Replies
itchy-amethyst
itchy-amethystOP•3y ago
TkDodo himself is here too, but I don't like pinging people, so I hope he also checks my question here 😅
correct-apricot
correct-apricot•3y ago
I think what happens in the gif is: 1. After clearing the console, you focus the page again so that triggers getFavorites() (refetch on focus) 2. While this is still in progress, you check the checkbox which starts toggleFavorite() and updates the cache (in onMutate) => the cb is checked 3. Then the first getFavorites() finishes and returns the favorite still not checked, so the cb gets unchecked 4. toggleFavorites() finishes and triggers another getFavorites() (because of the invalidation in onSettled(). 5. The second getFavorites() finishes and this time it returns the favorite as checked. The UI is updated and the cb is now checked. I think the fix would be to use queryClient.cancelQueries before updating the cache in step 2 (here https://github.com/NarniaT/favorites-react/blob/8c2b2560c287fe7d14eecb13cd485055cad875f0/src/hooks/useFavorites.ts#L32). That will cancel the getFavorites() started at step 1. The cancelQueries function is also used in the optimistic update section of the docs: https://tanstack.com/query/v4/docs/react/guides/optimistic-updates, I believe for the same reasons.
Overview | TanStack Query Docs
TanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze. Motivation
GitHub
favorites-react/useFavorites.ts at 8c2b2560c287fe7d14eecb13cd485055...
Contribute to NarniaT/favorites-react development by creating an account on GitHub.
itchy-amethyst
itchy-amethystOP•3y ago
Oh... Thank you Julien, you're so helpful 🥲, I initially tried cancelQueries too and it didn't work, but at that time I didn't have the isMutating === 0 check, so let me try them both together and see if it works this time.
fair-rose
fair-rose•3y ago
think you figured it out already 🙂 note that in v5, a mere focus from the devtools to your app will not trigger a refetchOnWindowFocus anymore 🙂
itchy-amethyst
itchy-amethystOP•3y ago
Yes, cancelling was required and fixed it, thank you both for your great help 💪 (I was exhaustively busy sorry for delayed thanking) In my case it was indeed maybe 'good' b/c it worked like React's strict mode to tell me cancellation is necessary, but I'm a noob with this library so certainly there have been good reasons for that decision.

Did you find this page helpful?