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,
+ )!
+}