1

I use react-query in my project, and I got a bug in my custom hook where I use useMutation query. I have all my custom hooks for queries in the file useApiData and useUpdateCase is one of them:

export const useUpdateCase = (caseId: number) => {
    const queryClient = useQueryClient();
    const [error, setError] = useState(undefined);

    const mutation = useMutation({
        mutationFn: (payload: UpdateCaseRequest): Promise<AxiosResponse<CaseDto>> =>
            CASE_API.api.updateCase(caseId, payload),
        onSuccess: (data) => {
            queryClient.setQueryData(["case", caseId], data);
            console.log("onSuccess");
            setError(undefined);
        },
        onError: (error) => {
            console.log("onError", error);
            setError(error);
        },
    });

    console.log("error", error);
    console.log("mutation", mutation);
    return { mutation, error };
};

Here in this custom hook, error is being logged properly, I am returning 503 with mock service worker:

rest.put(`${environment.url.case}/api/case/:caseId`, async (req, res, ctx) => {
            const body = await req.json();
            const existingCase = JSON.parse(localStorage.getItem(`case-${req.params.caseId}`));
            const updatedCase = { ...existingCase, ...body };

            const sucessHeaders = [
                ctx.set("Content-Type", "application/json"),
                ctx.status(200),
                ctx.body(JSON.stringify(updatedCase)),
            ];
            const errorHeaders = [
                ctx.status(503),
                ctx.json({
                    errorMessage: "Service Unavailable",
                }),
            ];

            const index = 1; //Math.random() < 0.9 ? 0 : 1;
            const response = [sucessHeaders, errorHeaders];

            // if (index === 0) {
            //     localStorage.setItem(`case-${req.params.caseId}`, JSON.stringify(updatedCase));
            // }

            return res(...response[index]);
        }),

But, in the component where I am calling this custom hook from:

const updateCase = useUpdateCase(caseId);

console.log("updateCase", updateCase);

On error updateCase is not updated here. Nothing is being logged while in the custom hook error is logged. So, in my console, logs look like this:

updateCase {mutation: {…}, error: undefined} useApiData.tsx:48 error AxiosError {message: 'Request failed with status code 503', name: 'AxiosError', code: 'ERR_BAD_RESPONSE', config: {…}, request: XMLHttpRequest, …} useApiData.tsx:49 mutation {context: undefined, data: undefined, error: AxiosError, failureCount: 1, failureReason: AxiosError, …}

What am I doing wrong here, why is the updated value not being propagated to a component where it was called from?

Leff
  • 1,968
  • 24
  • 97
  • 201

1 Answers1

1

From the query functions docs:

A query function can be literally any function that returns a promise. The promise that is returned should either resolve the data or throw an error.

Make sure the api.updateCase() throws an error.

E.g.

useUpdateCase.ts:

import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios, { AxiosResponse } from 'axios';
import { useState } from 'react';

type UpdateCaseRequest = any;
type CaseDto = any;

const api = {
  updateCase(id, payload) {
    return axios.put(`/api/case/${id}`, payload);
  },
};

export const useUpdateCase = (caseId: number) => {
  const queryClient = useQueryClient();
  const [error, setError] = useState<any>(undefined);

  const mutation = useMutation({
    mutationFn: (payload: UpdateCaseRequest): Promise<AxiosResponse<CaseDto> | undefined> =>
      api.updateCase(caseId, payload),
    onSuccess: (data) => {
      console.log('onSuccess');
      queryClient.setQueryData(['case', caseId], data);
      setError(undefined);
    },
    onError: (error) => {
      console.log('onError');
      setError(error);
    },
  });

  return { mutation, error };
};

useUpdateCase.test.tsx:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook } from '@testing-library/react-hooks';
import axios from 'axios';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import React from 'react';
import { useUpdateCase } from './useUpdateCase';

const server = setupServer(
  rest.put(`/api/case/:caseId`, async (req, res, ctx) => {
    const sucessHeaders = [ctx.set('Content-Type', 'application/json'), ctx.status(200), ctx.body(JSON.stringify({}))];
    const errorHeaders = [
      ctx.status(503),
      ctx.json({
        errorMessage: 'Service Unavailable',
      }),
    ];

    const index = 1;
    const response = [sucessHeaders, errorHeaders];

    return res(...response[index]);
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('67902700', () => {
  test('loads and displays greeting', async () => {
    const queryClient = new QueryClient({
      logger: {
        log: console.log,
        warn: console.warn,
        error: process.env.NODE_ENV === 'test' ? () => {} : console.error,
      },
      defaultOptions: {
        mutations: {
          retry: false,
          cacheTime: Infinity,
        },
      },
    });
    const { result, waitForNextUpdate } = renderHook(() => useUpdateCase(1), {
      wrapper: ({ children }) => <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>,
    });
    result.current.mutation.mutate({ text: 'new text' });
    await waitForNextUpdate();
    expect(axios.isAxiosError(result.current.error)).toBeTrue();
    expect(result.current.error.response.data.errorMessage).toEqual('Service Unavailable');
  });
});

Test result:

 PASS  stackoverflow/76033596/useUpdateCase.test.tsx
  67902700
    ✓ loads and displays greeting (81 ms)

  console.log
    onError

      at Object.onError (stackoverflow/76033596/useUpdateCase.ts:27:15)

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |   82.35 |      100 |      80 |   81.25 |                   
 useUpdateCase.ts |   82.35 |      100 |      80 |   81.25 | 22-24             
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.456 s, estimated 9 s
Ran all test suites related to changed files.

package versions:

"@tanstack/react-query": "^4.10.3",
"msw": "^0.29.0",
"react": "^16.14.0",
"@testing-library/react-hooks": "^8.0.1",
"axios": "^0.21.1",
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • additionally, let me please add that having a local `error` state is totally unnecessary. The mutations returns the `error` as part of the result object ... – TkDodo Apr 19 '23 at 19:52
  • @TkDodo yeah, I know. Just use OP’s code – Lin Du Apr 20 '23 at 00:26
  • so is the idea only to answer questions even if we know that the question does something that isn't right? I think it's good to note such things for posterity - it will help future readers I think. – TkDodo Apr 21 '23 at 16:48