Skip to content

Commit 16f57eb

Browse files
fix: do not manual refetch disabled queries (#37)
1 parent 584855c commit 16f57eb

File tree

6 files changed

+208
-2
lines changed

6 files changed

+208
-2
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import {QueryClient, useInfiniteQuery, useQuery} from '@tanstack/react-query';
2+
import {renderHook} from '@testing-library/react';
3+
4+
import {idle} from '../../../core';
5+
import type {AnyInfiniteQueryDataSource} from '../../impl/infinite/types';
6+
import type {AnyPlainQueryDataSource} from '../../impl/plain/types';
7+
import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch';
8+
import {useQueryContext} from '../useQueryContext';
9+
import {useQueryData} from '../useQueryData';
10+
11+
jest.mock('@tanstack/react-query', () => ({
12+
...jest.requireActual('@tanstack/react-query'),
13+
useQuery: jest.fn(),
14+
useInfiniteQuery: jest.fn(),
15+
}));
16+
17+
jest.mock('../useQueryContext');
18+
jest.mock('../../utils/warnDisabledRefetch');
19+
20+
const mockUseQuery = useQuery as jest.MockedFunction<typeof useQuery>;
21+
const mockUseInfiniteQuery = useInfiniteQuery as jest.MockedFunction<typeof useInfiniteQuery>;
22+
const mockWarnDisabledRefetch = warnDisabledRefetch as jest.MockedFunction<
23+
typeof warnDisabledRefetch
24+
>;
25+
26+
describe('useQueryData refetch behavior', () => {
27+
const mockQueryClient = new QueryClient();
28+
const mockContext = {queryClient: mockQueryClient};
29+
30+
beforeEach(() => {
31+
jest.clearAllMocks();
32+
(useQueryContext as jest.Mock).mockReturnValue(mockContext);
33+
});
34+
35+
const createMockQueryResult = (refetch = jest.fn()) => ({
36+
data: 'test-data',
37+
error: null,
38+
status: 'success' as const,
39+
fetchStatus: 'idle' as const,
40+
isLoading: false,
41+
isError: false,
42+
isSuccess: true,
43+
isPending: false,
44+
refetch,
45+
dataUpdatedAt: Date.now(),
46+
errorUpdatedAt: 0,
47+
failureCount: 0,
48+
failureReason: null,
49+
isFetched: true,
50+
isFetchedAfterMount: true,
51+
isFetching: false,
52+
isInitialLoading: false,
53+
isLoadingError: false,
54+
isPaused: false,
55+
isPlaceholderData: false,
56+
isPreviousData: false,
57+
isRefetchError: false,
58+
isRefetching: false,
59+
isStale: false,
60+
promise: Promise.resolve('test-data'),
61+
});
62+
63+
const createMockInfiniteResult = (refetch = jest.fn()) => ({
64+
data: {pages: [['item1'], ['item2']], pageParams: [undefined, 'next-page']},
65+
error: null,
66+
status: 'success' as const,
67+
fetchStatus: 'idle' as const,
68+
isLoading: false,
69+
isError: false,
70+
isSuccess: true,
71+
isPending: false,
72+
refetch,
73+
hasNextPage: false,
74+
hasPreviousPage: false,
75+
fetchNextPage: jest.fn(),
76+
fetchPreviousPage: jest.fn(),
77+
isFetchingNextPage: false,
78+
isFetchingPreviousPage: false,
79+
dataUpdatedAt: Date.now(),
80+
errorUpdatedAt: 0,
81+
failureCount: 0,
82+
failureReason: null,
83+
isFetched: true,
84+
isFetchedAfterMount: true,
85+
isFetching: false,
86+
isInitialLoading: false,
87+
isLoadingError: false,
88+
isPaused: false,
89+
isPlaceholderData: false,
90+
isPreviousData: false,
91+
isRefetchError: false,
92+
isRefetching: false,
93+
isStale: false,
94+
promise: Promise.resolve({
95+
pages: [['item1'], ['item2']],
96+
pageParams: [undefined, 'next-page'],
97+
}),
98+
});
99+
100+
describe('plain data source', () => {
101+
const plainDataSource: AnyPlainQueryDataSource = {
102+
type: 'plain',
103+
name: 'test-plain',
104+
fetch: jest.fn().mockResolvedValue({data: 'test-data'}),
105+
};
106+
107+
it('should use original refetch when no enabled option', () => {
108+
const originalRefetch = jest.fn();
109+
mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any);
110+
111+
const {result} = renderHook(() => useQueryData(plainDataSource, {id: 1}));
112+
113+
expect(result.current.refetch).toBe(originalRefetch);
114+
expect(result.current.refetch).not.toBe(mockWarnDisabledRefetch);
115+
});
116+
117+
it('should use warnDisabledRefetch when enabled: false', () => {
118+
const originalRefetch = jest.fn();
119+
mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any);
120+
121+
const {result} = renderHook(() =>
122+
useQueryData(plainDataSource, {id: 1}, {enabled: false}),
123+
);
124+
125+
expect(result.current.refetch).toBe(mockWarnDisabledRefetch);
126+
expect(result.current.refetch).not.toBe(originalRefetch);
127+
});
128+
129+
it('should use warnDisabledRefetch when params is idle', () => {
130+
const originalRefetch = jest.fn();
131+
mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any);
132+
133+
const {result} = renderHook(() => useQueryData(plainDataSource, idle));
134+
135+
expect(result.current.refetch).toBe(mockWarnDisabledRefetch);
136+
expect(result.current.refetch).not.toBe(originalRefetch);
137+
});
138+
});
139+
140+
describe('infinite data source', () => {
141+
const infiniteDataSource: AnyInfiniteQueryDataSource = {
142+
type: 'infinite',
143+
name: 'test-infinite',
144+
fetch: jest.fn().mockResolvedValue({data: ['item1', 'item2']}),
145+
next: jest.fn(),
146+
};
147+
148+
it('should use original refetch when no enabled option', () => {
149+
const originalRefetch = jest.fn();
150+
mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any);
151+
152+
const {result} = renderHook(() => useQueryData(infiniteDataSource, {id: 1}));
153+
154+
expect(result.current.refetch).toBe(originalRefetch);
155+
expect(result.current.refetch).not.toBe(mockWarnDisabledRefetch);
156+
});
157+
158+
it('should use warnDisabledRefetch when enabled: false', () => {
159+
const originalRefetch = jest.fn();
160+
mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any);
161+
162+
const {result} = renderHook(() =>
163+
useQueryData(infiniteDataSource, {id: 1}, {enabled: false}),
164+
);
165+
166+
expect(result.current.refetch).toBe(mockWarnDisabledRefetch);
167+
expect(result.current.refetch).not.toBe(originalRefetch);
168+
});
169+
170+
it('should use warnDisabledRefetch when params is idle', () => {
171+
const originalRefetch = jest.fn();
172+
mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any);
173+
174+
const {result} = renderHook(() => useQueryData(infiniteDataSource, idle));
175+
176+
expect(result.current.refetch).toBe(mockWarnDisabledRefetch);
177+
expect(result.current.refetch).not.toBe(originalRefetch);
178+
});
179+
});
180+
});

src/react-query/impl/infinite/hooks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useMemo} from 'react';
22

3-
import {useInfiniteQuery} from '@tanstack/react-query';
3+
import {skipToken, useInfiniteQuery} from '@tanstack/react-query';
44
import type {InfiniteData, InfiniteQueryObserverOptions} from '@tanstack/react-query';
55

66
import type {
@@ -16,6 +16,7 @@ import type {
1616
} from '../../../core';
1717
import {useRefetchInterval} from '../../hooks/useRefetchInterval';
1818
import {normalizeStatus} from '../../utils/normalizeStatus';
19+
import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch';
1920

2021
import type {AnyInfiniteQueryDataSource, InfiniteQueryObserverExtendedOptions} from './types';
2122
import {composeOptions} from './utils';
@@ -63,11 +64,14 @@ export const useInfiniteQueryData = <TDataSource extends AnyInfiniteQueryDataSou
6364
[state.data],
6465
);
6566

67+
const isDisabled = composedOptions.enabled === false || composedOptions.queryFn === skipToken;
68+
6669
return {
6770
...state,
6871
status: normalizeStatus(state.status, state.fetchStatus),
6972
data: transformedData,
7073
originalStatus: state.status,
7174
originalData: state.data,
75+
refetch: isDisabled ? warnDisabledRefetch : state.refetch,
7276
} as DataSourceState<TDataSource>;
7377
};

src/react-query/impl/plain/hooks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {type QueryObserverOptions, useQuery} from '@tanstack/react-query';
1+
import {type QueryObserverOptions, skipToken, useQuery} from '@tanstack/react-query';
22

33
import type {
44
DataSourceContext,
@@ -12,6 +12,7 @@ import type {
1212
} from '../../../core';
1313
import {useRefetchInterval} from '../../hooks/useRefetchInterval';
1414
import {normalizeStatus} from '../../utils/normalizeStatus';
15+
import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch';
1516

1617
import type {AnyPlainQueryDataSource, QueryObserverExtendedOptions} from './types';
1718
import {composeOptions} from './utils';
@@ -52,9 +53,12 @@ export const usePlainQueryData = <TDataSource extends AnyPlainQueryDataSource>(
5253
const composedOptions = usePlainQueryDataOptions(extendedOptions);
5354
const state = useQuery(composedOptions);
5455

56+
const isDisabled = composedOptions.enabled === false || composedOptions.queryFn === skipToken;
57+
5558
return {
5659
...state,
5760
status: normalizeStatus(state.status, state.fetchStatus),
5861
originalStatus: state.status,
62+
refetch: isDisabled ? warnDisabledRefetch : state.refetch,
5963
} as DataSourceState<TDataSource>;
6064
};

src/react-query/types/options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ export interface QueryDataAdditionalOptions<
99
TQueryKey extends QueryKey = QueryKey,
1010
> {
1111
refetchInterval?: RefetchInterval<TQueryFnData, TError, TQueryData, TQueryKey>;
12+
/**
13+
* @deprecated The use of the enabled option is deprecated.
14+
* It is recommended to use idle as query parameters to control query state.
15+
*/
16+
enabled?: boolean;
1217
}

src/react-query/utils/warn.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function warn(msg: string) {
2+
if (!msg || process.env.NODE_ENV === 'production') {
3+
return;
4+
}
5+
6+
// eslint-disable-next-line no-console
7+
console.warn(msg);
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {warn} from './warn';
2+
3+
export const warnDisabledRefetch = () => {
4+
warn('Disabled refetch is called');
5+
};

0 commit comments

Comments
 (0)