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 (
         <div>
           <div>isFetching: {isFetching}</div>
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<number> = []
     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<number> = []
     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<TResult = MutationState>(
   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<TResult = QueryState> = {
+  filters?: QueryFilters
+  select?: (query: Query<unknown, DefaultError, unknown, QueryKey>) => TResult
+}
+
+function getResult<TResult = QueryState>(
+  queryCache: QueryCache,
+  options: QueryStateOptions<TResult>,
+): Array<TResult> {
+  return queryCache
+    .findAll(options.filters)
+    .map(
+      (query): TResult =>
+        (options.select ? options.select(query) : query.state) as TResult,
+    )
+}
+
+export function useQueryState<TResult = QueryState>(
+  options: QueryStateOptions<TResult> = {},
+  queryClient?: QueryClient,
+): Array<TResult> {
+  const queryCache = useQueryClient(queryClient).getQueryCache()
+  const optionsRef = React.useRef(options)
+  const result = React.useRef<Array<TResult>>()
+  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,
+  )!
+}