1

I am trying to write the test case for an optimistic update in react query. But it's not working. Here is the code that I wrote to test it. Hope someone could help me. Thanks in advance. When I just write the onSuccess and leave an optimistic update, it works fine but here it's not working. And how can we mock the getQueryData and setQueryData here?

import { act, renderHook } from "@testing-library/react-hooks";
import axios from "axios";
import { createWrapper } from "../../test-utils";
import { useAddColorHook, useFetchColorHook } from "./usePaginationReactQuery";
jest.mock("axios");

describe('Testing custom hooks of react query', () => {

    it('Should add a new color', async () => {
        axios.post.mockReturnValue({data: [{label: 'Grey', id: 23}]})
        const { result, waitFor } = renderHook(() => useAddColorHook(1), { wrapper: createWrapper() });
        await act(() => {
            result.current.mutate({ label: 'Grey' })
        })
        await waitFor(() => result.current.isSuccess);
    })
})

export const createTestQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        cacheTime: Infinity,
      },
    },
    logger: {
      log: console.log,
      warn: console.warn,
      error: () => {},
    }
  });

export function createWrapper() {
  const testQueryClient = createTestQueryClient();
  return ({ children }) => (
    <QueryClientProvider client={testQueryClient}>
      {children}
    </QueryClientProvider>
  );
}
export const useAddColorHook = (page) => {
    const queryClient = useQueryClient()
    return useMutation(addColor, {
        // onSuccess: () => {
        //     queryClient.invalidateQueries(['colors', page])
        // }
        onMutate: async color => {
            // newHero refers to the argument being passed to the mutate function
            await queryClient.cancelQueries(['colors', page])
            const previousHeroData = queryClient.getQueryData(['colors', page])
            queryClient.setQueryData(['colors', page], (oldQueryData) => {
                return {
                    ...oldQueryData,
                    data: [...oldQueryData.data, { id: oldQueryData?.data?.length + 1, ...color }]
                }
            })
            return { previousHeroData }
        },
        onSuccess: (response, variables, context) => {
            queryClient.setQueryData(['colors', page], (oldQueryData) => {
                console.log(oldQueryData, 'oldQueryData', response, 'response', variables, 'var', context, 'context', 7984)
                return {
                    ...oldQueryData,
                    data: oldQueryData.data.map(data => data.label === variables.label ? response.data : data)
                }
            })
        },
        onError: (_err, _newTodo, context) => {
            queryClient.setQueryData(['colors', page], context.previousHeroData)
        },
        onSettled: () => {
            queryClient.invalidateQueries(['colors', page])
        }
    })
}
1
  • It's getting to onError due to this line const previousColorData = queryClient.getQueryData(['colors', page]) Maybe i need to mock this getQueryData but don't know how to do that Commented Oct 26, 2022 at 15:06

1 Answer 1

4

The error that you are getting actually shows a bug in the way you've implemented the optimistic update:

queryClient.setQueryData(['colors', page], (oldQueryData) => {
  return {
    ...oldQueryData,
    data: [...oldQueryData.data, { id: oldQueryData?.data?.length + 1, ...color }]
  }
})

what if there is no entry in the query cache that matches this query key? oldQueryData will be undefined, but you're not guarding against that, you are spreading ...oldQueryData.data and this will error out at runtime.

This is what happens in your test because you start with a fresh query cache for every test.

An easy way out would be, since you have previousHeroData already:

const previousHeroData = queryClient.getQueryData(['colors', page])
if (previousHeroData) {
  queryClient.setQueryData(['colors', page], {
    ...previousHeroData,
    data: [...previousHeroData.data, { id: previousHeroData.data.length + 1, ...color }]
  }
}

If you are using TanStack/query v4, you can also return undefined from the updater function. This doesn't work in v3 though:

queryClient.setQueryData(['colors', page], (oldQueryData) => {
    return oldQueryData ? {
        ...oldQueryData,
        data: [...oldQueryData.data, { id: oldQueryData?.data?.length + 1, ...color }]
    } : undefined
})

This doesn't perform an optimistic update then though. If you know how to create a valid cache entry from undefined previous data, you can of course also do that.

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

2 Comments

Thank you for the time you dedicated here, but I have one more question. Is there a way that we can store some cached value for this respective key and get the previousHeroData and oldQueryData.
in your test, you can set queryClient.setQueryData(key, data) to put data in the cache.

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.