diff --git a/packages/react-query/src/__tests__/useIsFetching.test.tsx b/packages/react-query/src/__tests__/useIsFetching.test.tsx index 22fa478f4f..e51f05c466 100644 --- a/packages/react-query/src/__tests__/useIsFetching.test.tsx +++ b/packages/react-query/src/__tests__/useIsFetching.test.tsx @@ -205,6 +205,8 @@ describe('useIsFetching', () => { const key = queryKey() function Page() { + const isFetching = useIsFetching({}, queryClient) + useQuery( { queryKey: key, @@ -216,8 +218,6 @@ describe('useIsFetching', () => { queryClient, ) - const isFetching = useIsFetching({}, queryClient) - return (
isFetching: {isFetching}
diff --git a/packages/react-query/src/__tests__/useMutationState.test.tsx b/packages/react-query/src/__tests__/useMutationState.test.tsx index 24a50de691..e77507b14a 100644 --- a/packages/react-query/src/__tests__/useMutationState.test.tsx +++ b/packages/react-query/src/__tests__/useMutationState.test.tsx @@ -66,12 +66,15 @@ describe('useIsMutating', () => { const isMutatingArray: Array = [] const queryClient = createQueryClient() - function IsMutating() { + function IsMutatingBase() { const isMutating = useIsMutating({ mutationKey: ['mutation1'] }) isMutatingArray.push(isMutating) return null } + // Memo to avoid other `useMutation` hook causing a re-render + const IsMutating = React.memo(IsMutatingBase) + function Page() { const { mutate: mutate1 } = useMutation({ mutationKey: ['mutation1'], @@ -104,7 +107,7 @@ describe('useIsMutating', () => { const isMutatingArray: Array = [] const queryClient = createQueryClient() - function IsMutating() { + function IsMutatingBase() { const isMutating = useIsMutating({ predicate: (mutation) => mutation.options.mutationKey?.[0] === 'mutation1', @@ -113,6 +116,8 @@ describe('useIsMutating', () => { return null } + const IsMutating = React.memo(IsMutatingBase) + function Page() { const { mutate: mutate1 } = useMutation({ mutationKey: ['mutation1'], diff --git a/packages/react-query/src/index.ts b/packages/react-query/src/index.ts index 926c673a6d..099ebeb9a4 100644 --- a/packages/react-query/src/index.ts +++ b/packages/react-query/src/index.ts @@ -40,6 +40,7 @@ export { } from './QueryErrorResetBoundary' export { useIsFetching } from './useIsFetching' export { useIsMutating, useMutationState } from './useMutationState' +export { useQueryState } from './useQueryState' export { useMutation } from './useMutation' export { useInfiniteQuery } from './useInfiniteQuery' export { useIsRestoring, IsRestoringProvider } from './isRestoring' diff --git a/packages/react-query/src/useIsFetching.ts b/packages/react-query/src/useIsFetching.ts index a6252912f2..cb7045d8d2 100644 --- a/packages/react-query/src/useIsFetching.ts +++ b/packages/react-query/src/useIsFetching.ts @@ -1,24 +1,13 @@ 'use client' -import * as React from 'react' -import { notifyManager } from '@tanstack/query-core' - -import { useQueryClient } from './QueryClientProvider' +import { useQueryState } from './useQueryState' import type { QueryClient, QueryFilters } from '@tanstack/query-core' export function useIsFetching( filters?: QueryFilters, queryClient?: QueryClient, ): number { - const client = useQueryClient(queryClient) - const queryCache = client.getQueryCache() - - return React.useSyncExternalStore( - React.useCallback( - (onStoreChange) => - queryCache.subscribe(notifyManager.batchCalls(onStoreChange)), - [queryCache], - ), - () => client.isFetching(filters), - () => client.isFetching(filters), - ) + return useQueryState( + { filters: { ...filters, fetchStatus: 'fetching' } }, + queryClient, + ).length } diff --git a/packages/react-query/src/useMutationState.ts b/packages/react-query/src/useMutationState.ts index d14ebc46b7..61054a8180 100644 --- a/packages/react-query/src/useMutationState.ts +++ b/packages/react-query/src/useMutationState.ts @@ -64,19 +64,20 @@ export function useMutationState( return React.useSyncExternalStore( React.useCallback( (onStoreChange) => - mutationCache.subscribe(() => { - const nextResult = replaceEqualDeep( - result.current, - getResult(mutationCache, optionsRef.current), - ) - if (result.current !== nextResult) { - result.current = nextResult - notifyManager.schedule(onStoreChange) - } - }), + mutationCache.subscribe(notifyManager.batchCalls(onStoreChange)), [mutationCache], ), - () => result.current, + () => { + const nextResult = replaceEqualDeep( + result.current, + getResult(mutationCache, optionsRef.current), + ) + if (result.current !== nextResult) { + result.current = nextResult + } + + return result.current + }, () => result.current, )! } diff --git a/packages/react-query/src/useQueryState.ts b/packages/react-query/src/useQueryState.ts new file mode 100644 index 0000000000..81e722e092 --- /dev/null +++ b/packages/react-query/src/useQueryState.ts @@ -0,0 +1,67 @@ +'use client' +import * as React from 'react' + +import { notifyManager, replaceEqualDeep } from '@tanstack/query-core' +import { useQueryClient } from './QueryClientProvider' +import type { + DefaultError, + Query, + QueryCache, + QueryClient, + QueryFilters, + QueryKey, + QueryState, +} from '@tanstack/query-core' + +type QueryStateOptions = { + filters?: QueryFilters + select?: (query: Query) => TResult +} + +function getResult( + queryCache: QueryCache, + options: QueryStateOptions, +): Array { + return queryCache + .findAll(options.filters) + .map( + (query): TResult => + (options.select ? options.select(query) : query.state) as TResult, + ) +} + +export function useQueryState( + options: QueryStateOptions = {}, + queryClient?: QueryClient, +): Array { + const queryCache = useQueryClient(queryClient).getQueryCache() + const optionsRef = React.useRef(options) + const result = React.useRef>() + if (!result.current) { + result.current = getResult(queryCache, options) + } + + React.useEffect(() => { + optionsRef.current = options + }) + + return React.useSyncExternalStore( + React.useCallback( + (onStoreChange) => + queryCache.subscribe(notifyManager.batchCalls(onStoreChange)), + [queryCache], + ), + () => { + const nextResult = replaceEqualDeep( + result.current, + getResult(queryCache, optionsRef.current), + ) + if (result.current !== nextResult) { + result.current = nextResult + } + + return result.current + }, + () => result.current, + )! +}