React Query自定义钩子未将错误状态传播到调用它的组件。

huangapple go评论65阅读模式
英文:

React query custom hook not propagating error state to component where it was called from

问题

我在项目中使用了react-query,但在使用useMutation查询的自定义挂钩中遇到了一个错误。在这个自定义挂钩中,错误被正确记录,但在调用这个自定义挂钩的组件中,updateCase在错误时没有被更新。在控制台中,日志如下所示:

> 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, …}

我在这里做错了什么?为什么更新后的值没有传播到调用它的组件中?

英文:

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?

答案1

得分: 1

以下是您要翻译的内容:

查询函数文档中:

> 查询函数可以是任何返回承诺的函数。返回的承诺应该要么解析数据,要么抛出错误

确保 api.updateCase() 抛出错误。

例如:

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

测试结果:

 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.

包版本:

"@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",
英文:

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 &#39;@tanstack/react-query&#39;;
import axios, { AxiosResponse } from &#39;axios&#39;;
import { useState } from &#39;react&#39;;

type UpdateCaseRequest = any;
type CaseDto = any;

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

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

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

  return { mutation, error };
};

useUpdateCase.test.tsx:

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

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

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

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

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

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

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:

&quot;@tanstack/react-query&quot;: &quot;^4.10.3&quot;,
&quot;msw&quot;: &quot;^0.29.0&quot;,
&quot;react&quot;: &quot;^16.14.0&quot;,
&quot;@testing-library/react-hooks&quot;: &quot;^8.0.1&quot;,
&quot;axios&quot;: &quot;^0.21.1&quot;,

huangapple
  • 本文由 发表于 2023年4月17日 17:24:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76033596.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定