diff --git a/CHANGELOG.md b/CHANGELOG.md index 40068d8..b2e27cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,127 +2,108 @@ ## [0.8.2](https://github.com/gravity-ui/data-source/compare/v0.8.1...v0.8.2) (2025-12-12) - ### Bug Fixes -* revert "do not manual refetch disabled queries" ([#43](https://github.com/gravity-ui/data-source/issues/43)) ([ac29b67](https://github.com/gravity-ui/data-source/commit/ac29b67250a2749c584a5b74503e2c1b81f3977f)) +- revert "do not manual refetch disabled queries" ([#43](https://github.com/gravity-ui/data-source/issues/43)) ([ac29b67](https://github.com/gravity-ui/data-source/commit/ac29b67250a2749c584a5b74503e2c1b81f3977f)) ## [0.8.1](https://github.com/gravity-ui/data-source/compare/v0.8.0...v0.8.1) (2025-12-08) - ### Bug Fixes -* normy fix add update function ([#41](https://github.com/gravity-ui/data-source/issues/41)) ([74d8cce](https://github.com/gravity-ui/data-source/commit/74d8ccec6b0a0221b38d2411b305b964171a9d26)) +- normy fix add update function ([#41](https://github.com/gravity-ui/data-source/issues/41)) ([74d8cce](https://github.com/gravity-ui/data-source/commit/74d8ccec6b0a0221b38d2411b305b964171a9d26)) ## [0.8.0](https://github.com/gravity-ui/data-source/compare/v0.7.0...v0.8.0) (2025-12-03) - ### Features -* add normalize ([#40](https://github.com/gravity-ui/data-source/issues/40)) ([70b9dd4](https://github.com/gravity-ui/data-source/commit/70b9dd40755c9e1f329f4c7743d3edba3f97e6f0)) - +- add normalize ([#40](https://github.com/gravity-ui/data-source/issues/40)) ([70b9dd4](https://github.com/gravity-ui/data-source/commit/70b9dd40755c9e1f329f4c7743d3edba3f97e6f0)) ### Bug Fixes -* do not manual refetch disabled queries ([#37](https://github.com/gravity-ui/data-source/issues/37)) ([16f57eb](https://github.com/gravity-ui/data-source/commit/16f57eb7f46cad6960585d89d4b29621a52f4ba3)) +- do not manual refetch disabled queries ([#37](https://github.com/gravity-ui/data-source/issues/37)) ([16f57eb](https://github.com/gravity-ui/data-source/commit/16f57eb7f46cad6960585d89d4b29621a52f4ba3)) ## [0.7.0](https://github.com/gravity-ui/data-source/compare/v0.6.1...v0.7.0) (2025-06-02) - ### Features -* add the withCatch function ([#34](https://github.com/gravity-ui/data-source/issues/34)) ([85ac92c](https://github.com/gravity-ui/data-source/commit/85ac92c4cb55b92c10af99a9d1f4cf75fb9739fe)) -* remove the transformError ([#36](https://github.com/gravity-ui/data-source/issues/36)) ([01e2e9a](https://github.com/gravity-ui/data-source/commit/01e2e9a6e6af7c9e06d99add06c6db55646552cb)) +- add the withCatch function ([#34](https://github.com/gravity-ui/data-source/issues/34)) ([85ac92c](https://github.com/gravity-ui/data-source/commit/85ac92c4cb55b92c10af99a9d1f4cf75fb9739fe)) +- remove the transformError ([#36](https://github.com/gravity-ui/data-source/issues/36)) ([01e2e9a](https://github.com/gravity-ui/data-source/commit/01e2e9a6e6af7c9e06d99add06c6db55646552cb)) ## [0.6.1](https://github.com/gravity-ui/data-source/compare/v0.6.0...v0.6.1) (2025-03-25) - ### Bug Fixes -* fix typo and hasTag function ([#29](https://github.com/gravity-ui/data-source/issues/29)) ([a0f1ae1](https://github.com/gravity-ui/data-source/commit/a0f1ae183d3b41b1e1b3bf0575b2f0bc3af16efb)) +- fix typo and hasTag function ([#29](https://github.com/gravity-ui/data-source/issues/29)) ([a0f1ae1](https://github.com/gravity-ui/data-source/commit/a0f1ae183d3b41b1e1b3bf0575b2f0bc3af16efb)) ## [0.6.0](https://github.com/gravity-ui/data-source/compare/v0.5.1...v0.6.0) (2025-03-25) - ### Features -* add the transformError data-source function ([#24](https://github.com/gravity-ui/data-source/issues/24)) ([16c1775](https://github.com/gravity-ui/data-source/commit/16c177512412d96ab14bfa9f3f88610b04f65a36)) - +- add the transformError data-source function ([#24](https://github.com/gravity-ui/data-source/issues/24)) ([16c1775](https://github.com/gravity-ui/data-source/commit/16c177512412d96ab14bfa9f3f88610b04f65a36)) ### Bug Fixes -* add NoInfer ([#28](https://github.com/gravity-ui/data-source/issues/28)) ([c3d6c93](https://github.com/gravity-ui/data-source/commit/c3d6c93c50f1e59f558aed0dc35c797800025045)) +- add NoInfer ([#28](https://github.com/gravity-ui/data-source/issues/28)) ([c3d6c93](https://github.com/gravity-ui/data-source/commit/c3d6c93c50f1e59f558aed0dc35c797800025045)) ## [0.5.1](https://github.com/gravity-ui/data-source/compare/v0.5.0...v0.5.1) (2025-03-14) - ### Bug Fixes -* **react-query:** fix queryFn for useRefetchInterval ([#25](https://github.com/gravity-ui/data-source/issues/25)) ([8ba00e6](https://github.com/gravity-ui/data-source/commit/8ba00e680e4227ac9b47ebba709a2678e39b2781)) +- **react-query:** fix queryFn for useRefetchInterval ([#25](https://github.com/gravity-ui/data-source/issues/25)) ([8ba00e6](https://github.com/gravity-ui/data-source/commit/8ba00e680e4227ac9b47ebba709a2678e39b2781)) ## [0.5.0](https://github.com/gravity-ui/data-source/compare/v0.4.0...v0.5.0) (2025-02-21) - ### Features -* add progressive refetch interval and repeat invalidation ([#22](https://github.com/gravity-ui/data-source/issues/22)) ([c472fae](https://github.com/gravity-ui/data-source/commit/c472faed04ad70129a0ba5ce027fc079550c4e6f)) +- add progressive refetch interval and repeat invalidation ([#22](https://github.com/gravity-ui/data-source/issues/22)) ([c472fae](https://github.com/gravity-ui/data-source/commit/c472faed04ad70129a0ba5ce027fc079550c4e6f)) ## [0.4.0](https://github.com/gravity-ui/data-source/compare/v0.3.0...v0.4.0) (2024-08-19) - ### Features -* **react-query:** add memoization for data in infinite source ([#17](https://github.com/gravity-ui/data-source/issues/17)) ([1147af1](https://github.com/gravity-ui/data-source/commit/1147af1526f3969c894128cfc83694a690582a5b)) +- **react-query:** add memoization for data in infinite source ([#17](https://github.com/gravity-ui/data-source/issues/17)) ([1147af1](https://github.com/gravity-ui/data-source/commit/1147af1526f3969c894128cfc83694a690582a5b)) ## [0.3.0](https://github.com/gravity-ui/data-source/compare/v0.2.1...v0.3.0) (2024-08-12) - ### Features -* **react-query:** update to major v5 ([#13](https://github.com/gravity-ui/data-source/issues/13)) ([9e6b893](https://github.com/gravity-ui/data-source/commit/9e6b89318ce26071321e37eb720890749a3031f6)) +- **react-query:** update to major v5 ([#13](https://github.com/gravity-ui/data-source/issues/13)) ([9e6b893](https://github.com/gravity-ui/data-source/commit/9e6b89318ce26071321e37eb720890749a3031f6)) ## [0.2.1](https://github.com/gravity-ui/data-source/compare/v0.2.0...v0.2.1) (2024-08-07) - ### ⚠ BREAKING CHANGES -* `next` and `prev` are `Partial | undefined` instead of `Partial | undefined` -* DataSource no longer supports in `ActualParams` type - +- `next` and `prev` are `Partial | undefined` instead of `Partial | undefined` +- DataSource no longer supports in `ActualParams` type ### Bug Fixes -* make page param patch for request ([#11](https://github.com/gravity-ui/data-source/issues/11)) ([e76f88e](https://github.com/gravity-ui/data-source/commit/e76f88e8426d24c32df9615f5a678f923bb2c84e)) +- make page param patch for request ([#11](https://github.com/gravity-ui/data-source/issues/11)) ([e76f88e](https://github.com/gravity-ui/data-source/commit/e76f88e8426d24c32df9615f5a678f923bb2c84e)) ## [0.2.0](https://github.com/gravity-ui/data-source/compare/v0.1.2...v0.2.0) (2024-08-05) - ### Features -* **react:** add custom props for data loader components ([#8](https://github.com/gravity-ui/data-source/issues/8)) ([b4d12de](https://github.com/gravity-ui/data-source/commit/b4d12dea1e94f3267732698f9f4a20aa90c6baac)) +- **react:** add custom props for data loader components ([#8](https://github.com/gravity-ui/data-source/issues/8)) ([b4d12de](https://github.com/gravity-ui/data-source/commit/b4d12dea1e94f3267732698f9f4a20aa90c6baac)) ## [0.1.2](https://github.com/gravity-ui/data-source/compare/v0.1.1...v0.1.2) (2024-08-05) - ### Bug Fixes -* install utlity-types package as dependency ([#6](https://github.com/gravity-ui/data-source/issues/6)) ([783c929](https://github.com/gravity-ui/data-source/commit/783c929d3945a55ef15b26df1edf20850ef780fa)) +- install utlity-types package as dependency ([#6](https://github.com/gravity-ui/data-source/issues/6)) ([783c929](https://github.com/gravity-ui/data-source/commit/783c929d3945a55ef15b26df1edf20850ef780fa)) ## [0.1.1](https://github.com/gravity-ui/data-source/compare/v0.1.0...v0.1.1) (2024-08-02) - ### Bug Fixes -* **react:** add exports for error props ([#4](https://github.com/gravity-ui/data-source/issues/4)) ([a86c45b](https://github.com/gravity-ui/data-source/commit/a86c45b70b9119b8f276ea0140c78b3d9c02060e)) +- **react:** add exports for error props ([#4](https://github.com/gravity-ui/data-source/issues/4)) ([a86c45b](https://github.com/gravity-ui/data-source/commit/a86c45b70b9119b8f276ea0140c78b3d9c02060e)) ## 0.1.0 (2024-07-29) - ### chore -* release 0.1.0 ([3eea73e](https://github.com/gravity-ui/data-source/commit/3eea73effce4ce197f1f8ac305211cb2919a88c5)) - +- release 0.1.0 ([3eea73e](https://github.com/gravity-ui/data-source/commit/3eea73effce4ce197f1f8ac305211cb2919a88c5)) ### Features -* add base code ([a2cfd40](https://github.com/gravity-ui/data-source/commit/a2cfd4019bc2a5f7697f6e31aa40ed30990c4117)) +- add base code ([a2cfd40](https://github.com/gravity-ui/data-source/commit/a2cfd4019bc2a5f7697f6e31aa40ed30990c4117)) diff --git a/src/react-query/hooks/__tests__/useQueryData.refetch.test.ts b/src/react-query/hooks/__tests__/useQueryData.refetch.test.ts new file mode 100644 index 0000000..cde00b3 --- /dev/null +++ b/src/react-query/hooks/__tests__/useQueryData.refetch.test.ts @@ -0,0 +1,189 @@ +import {QueryClient, useInfiniteQuery, useQuery} from '@tanstack/react-query'; +import {renderHook} from '@testing-library/react'; + +import {idle} from '../../../core'; +import type {AnyInfiniteQueryDataSource} from '../../impl/infinite/types'; +import type {AnyPlainQueryDataSource} from '../../impl/plain/types'; +import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch'; +import {useQueryContext} from '../useQueryContext'; +import {useQueryData} from '../useQueryData'; + +jest.mock('@tanstack/react-query', () => ({ + ...jest.requireActual('@tanstack/react-query'), + useQuery: jest.fn(), + useInfiniteQuery: jest.fn(), +})); + +jest.mock('../useQueryContext'); +jest.mock('../../utils/warnDisabledRefetch'); + +const mockUseQuery = useQuery as jest.MockedFunction; +const mockUseInfiniteQuery = useInfiniteQuery as jest.MockedFunction; +const mockWarnDisabledRefetch = warnDisabledRefetch as jest.MockedFunction< + typeof warnDisabledRefetch +>; + +describe('useQueryData refetch behavior', () => { + const mockQueryClient = new QueryClient(); + const mockContext = {queryClient: mockQueryClient}; + + beforeEach(() => { + jest.clearAllMocks(); + (useQueryContext as jest.Mock).mockReturnValue(mockContext); + }); + + const createMockQueryResult = (refetch = jest.fn()) => ({ + data: 'test-data', + error: null, + status: 'success' as const, + fetchStatus: 'idle' as const, + isLoading: false, + isError: false, + isSuccess: true, + isPending: false, + refetch, + dataUpdatedAt: Date.now(), + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isInitialLoading: false, + isLoadingError: false, + isPaused: false, + isPlaceholderData: false, + isPreviousData: false, + isRefetchError: false, + isRefetching: false, + isStale: false, + promise: Promise.resolve('test-data'), + }); + + const createMockInfiniteResult = (refetch = jest.fn()) => ({ + data: {pages: [['item1'], ['item2']], pageParams: [undefined, 'next-page']}, + error: null, + status: 'success' as const, + fetchStatus: 'idle' as const, + isLoading: false, + isError: false, + isSuccess: true, + isPending: false, + refetch, + hasNextPage: false, + hasPreviousPage: false, + fetchNextPage: jest.fn(), + fetchPreviousPage: jest.fn(), + isFetchingNextPage: false, + isFetchingPreviousPage: false, + dataUpdatedAt: Date.now(), + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isInitialLoading: false, + isLoadingError: false, + isPaused: false, + isPlaceholderData: false, + isPreviousData: false, + isRefetchError: false, + isRefetching: false, + isStale: false, + promise: Promise.resolve({ + pages: [['item1'], ['item2']], + pageParams: [undefined, 'next-page'], + }), + }); + + describe('plain data source', () => { + const plainDataSource: AnyPlainQueryDataSource = { + type: 'plain', + name: 'test-plain', + fetch: jest.fn().mockResolvedValue({data: 'test-data'}), + }; + + it('should wrap refetch when no enabled option', async () => { + const originalRefetch = jest.fn().mockResolvedValue({data: 'test', status: 'success'}); + mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any); + + const {result} = renderHook(() => useQueryData(plainDataSource, {id: 1})); + + expect(result.current.refetch).not.toBe(originalRefetch); + expect(result.current.refetch).not.toBe(mockWarnDisabledRefetch); + + await result.current.refetch(); + expect(originalRefetch).toHaveBeenCalledTimes(1); + }); + + it('should use warnDisabledRefetch when enabled: false', () => { + const originalRefetch = jest.fn(); + mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any); + + const {result} = renderHook(() => + useQueryData(plainDataSource, {id: 1}, {enabled: false}), + ); + + expect(result.current.refetch).toBe(mockWarnDisabledRefetch); + expect(result.current.refetch).not.toBe(originalRefetch); + }); + + it('should use warnDisabledRefetch when params is idle', () => { + const originalRefetch = jest.fn(); + mockUseQuery.mockReturnValue(createMockQueryResult(originalRefetch) as any); + + const {result} = renderHook(() => useQueryData(plainDataSource, idle)); + + expect(result.current.refetch).toBe(mockWarnDisabledRefetch); + expect(result.current.refetch).not.toBe(originalRefetch); + }); + }); + + describe('infinite data source', () => { + const infiniteDataSource: AnyInfiniteQueryDataSource = { + type: 'infinite', + name: 'test-infinite', + fetch: jest.fn().mockResolvedValue({data: ['item1', 'item2']}), + next: jest.fn(), + }; + + it('should wrap refetch when no enabled option', async () => { + const originalRefetch = jest.fn().mockResolvedValue({ + data: {pages: [], pageParams: []}, + status: 'success', + }); + mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any); + + const {result} = renderHook(() => useQueryData(infiniteDataSource, {id: 1})); + + expect(result.current.refetch).not.toBe(originalRefetch); + expect(result.current.refetch).not.toBe(mockWarnDisabledRefetch); + + await result.current.refetch(); + expect(originalRefetch).toHaveBeenCalledTimes(1); + }); + + it('should use warnDisabledRefetch when enabled: false', () => { + const originalRefetch = jest.fn(); + mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any); + + const {result} = renderHook(() => + useQueryData(infiniteDataSource, {id: 1}, {enabled: false}), + ); + + expect(result.current.refetch).toBe(mockWarnDisabledRefetch); + expect(result.current.refetch).not.toBe(originalRefetch); + }); + + it('should use warnDisabledRefetch when params is idle', () => { + const originalRefetch = jest.fn(); + mockUseInfiniteQuery.mockReturnValue(createMockInfiniteResult(originalRefetch) as any); + + const {result} = renderHook(() => useQueryData(infiniteDataSource, idle)); + + expect(result.current.refetch).toBe(mockWarnDisabledRefetch); + expect(result.current.refetch).not.toBe(originalRefetch); + }); + }); +}); diff --git a/src/react-query/impl/infinite/hooks.ts b/src/react-query/impl/infinite/hooks.ts index 6f30a1b..2a0e8c1 100644 --- a/src/react-query/impl/infinite/hooks.ts +++ b/src/react-query/impl/infinite/hooks.ts @@ -1,6 +1,6 @@ import {useMemo} from 'react'; -import {useInfiniteQuery} from '@tanstack/react-query'; +import {skipToken, useInfiniteQuery} from '@tanstack/react-query'; import type {InfiniteData, InfiniteQueryObserverOptions} from '@tanstack/react-query'; import type { @@ -16,6 +16,8 @@ import type { } from '../../../core'; import {useRefetchInterval} from '../../hooks/useRefetchInterval'; import {normalizeStatus} from '../../utils/normalizeStatus'; +import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch'; +import {wrapRefetch} from '../../utils/wrapRefetch'; import type {AnyInfiniteQueryDataSource, InfiniteQueryObserverExtendedOptions} from './types'; import {composeOptions} from './utils'; @@ -63,11 +65,14 @@ export const useInfiniteQueryData = ; }; diff --git a/src/react-query/impl/infinite/types.ts b/src/react-query/impl/infinite/types.ts index 042f302..a9ead6d 100644 --- a/src/react-query/impl/infinite/types.ts +++ b/src/react-query/impl/infinite/types.ts @@ -84,6 +84,7 @@ type ResultWrapper = { status: DataLoaderStatus; data: Array>, 1>>; + refetch: () => Promise; } > & { originalStatus: TResult['status']; diff --git a/src/react-query/impl/plain/hooks.ts b/src/react-query/impl/plain/hooks.ts index b6c4063..ba4de78 100644 --- a/src/react-query/impl/plain/hooks.ts +++ b/src/react-query/impl/plain/hooks.ts @@ -1,4 +1,4 @@ -import {type QueryObserverOptions, useQuery} from '@tanstack/react-query'; +import {type QueryObserverOptions, skipToken, useQuery} from '@tanstack/react-query'; import type { DataSourceContext, @@ -12,6 +12,8 @@ import type { } from '../../../core'; import {useRefetchInterval} from '../../hooks/useRefetchInterval'; import {normalizeStatus} from '../../utils/normalizeStatus'; +import {warnDisabledRefetch} from '../../utils/warnDisabledRefetch'; +import {wrapRefetch} from '../../utils/wrapRefetch'; import type {AnyPlainQueryDataSource, QueryObserverExtendedOptions} from './types'; import {composeOptions} from './utils'; @@ -52,9 +54,12 @@ export const usePlainQueryData = ( const composedOptions = usePlainQueryDataOptions(extendedOptions); const state = useQuery(composedOptions); + const isDisabled = composedOptions.enabled === false || composedOptions.queryFn === skipToken; + return { ...state, status: normalizeStatus(state.status, state.fetchStatus), originalStatus: state.status, + refetch: isDisabled ? warnDisabledRefetch : wrapRefetch(state.refetch), } as DataSourceState; }; diff --git a/src/react-query/impl/plain/types.ts b/src/react-query/impl/plain/types.ts index 381898e..0b34d4d 100644 --- a/src/react-query/impl/plain/types.ts +++ b/src/react-query/impl/plain/types.ts @@ -53,5 +53,11 @@ export type AnyPlainQueryDataSource = PlainQueryDataSource = TResult extends QueryObserverResult, TError> - ? Overwrite & {originalStatus: TResult['status']} + ? Overwrite< + TResult, + { + status: DataLoaderStatus; + refetch: () => Promise; + } + > & {originalStatus: TResult['status']} : never; diff --git a/src/react-query/types/options.ts b/src/react-query/types/options.ts index 27b6839..f04eebe 100644 --- a/src/react-query/types/options.ts +++ b/src/react-query/types/options.ts @@ -11,6 +11,11 @@ export interface QueryDataAdditionalOptions< TQueryKey extends QueryKey = QueryKey, > { refetchInterval?: RefetchInterval; + /** + * @deprecated The use of the enabled option is deprecated. + * It is recommended to use idle as query parameters to control query state. + */ + enabled?: boolean; /** Normalization configuration (enable/disable) */ normalize?: boolean; /** Optimistic data update configuration */ diff --git a/src/react-query/utils/warn.ts b/src/react-query/utils/warn.ts new file mode 100644 index 0000000..061762f --- /dev/null +++ b/src/react-query/utils/warn.ts @@ -0,0 +1,8 @@ +export function warn(msg: string) { + if (!msg || process.env.NODE_ENV === 'production') { + return; + } + + // eslint-disable-next-line no-console + console.warn(msg); +} diff --git a/src/react-query/utils/warnDisabledRefetch.ts b/src/react-query/utils/warnDisabledRefetch.ts new file mode 100644 index 0000000..b3dfd79 --- /dev/null +++ b/src/react-query/utils/warnDisabledRefetch.ts @@ -0,0 +1,5 @@ +import {warn} from './warn'; + +export const warnDisabledRefetch = async (): Promise => { + warn('Disabled refetch is called'); +}; diff --git a/src/react-query/utils/wrapRefetch.ts b/src/react-query/utils/wrapRefetch.ts new file mode 100644 index 0000000..d7b0026 --- /dev/null +++ b/src/react-query/utils/wrapRefetch.ts @@ -0,0 +1,9 @@ +import type {QueryObserverResult, RefetchOptions} from '@tanstack/react-query'; + +export function wrapRefetch( + refetch: (options?: RefetchOptions) => Promise>, +): (options?: RefetchOptions) => Promise { + return async (options?: RefetchOptions) => { + await refetch(options); + }; +}