diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx
index 9f0cbce408..d0f8817912 100644
--- a/packages/react-query/src/__tests__/useQuery.test.tsx
+++ b/packages/react-query/src/__tests__/useQuery.test.tsx
@@ -5964,6 +5964,110 @@ describe('useQuery', () => {
})
})
+ describe('subscribed', () => {
+ it('should be able to toggle subscribed', async () => {
+ const key = queryKey()
+ const queryFn = vi.fn(async () => 'data')
+ function Page() {
+ const [subscribed, setSubscribed] = React.useState(true)
+ const { data } = useQuery({
+ queryKey: key,
+ queryFn,
+ subscribed,
+ })
+ return (
+
+ data: {data}
+
+
+ )
+ }
+
+ const rendered = renderWithClient(queryClient, )
+ await waitFor(() => rendered.getByText('data: data'))
+
+ expect(
+ queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
+ ).toBe(1)
+
+ fireEvent.click(rendered.getByRole('button', { name: 'toggle' }))
+
+ expect(
+ queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
+ ).toBe(0)
+
+ expect(queryFn).toHaveBeenCalledTimes(1)
+
+ fireEvent.click(rendered.getByRole('button', { name: 'toggle' }))
+
+ // background refetch when we re-subscribe
+ await waitFor(() => expect(queryFn).toHaveBeenCalledTimes(2))
+ expect(
+ queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
+ ).toBe(1)
+ })
+
+ it('should not be attached to the query when subscribed is false', async () => {
+ const key = queryKey()
+ const queryFn = vi.fn(async () => 'data')
+ function Page() {
+ const { data } = useQuery({
+ queryKey: key,
+ queryFn,
+ subscribed: false,
+ })
+ return (
+
+ data: {data}
+
+ )
+ }
+
+ const rendered = renderWithClient(queryClient, )
+ await waitFor(() => rendered.getByText('data:'))
+
+ expect(
+ queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
+ ).toBe(0)
+
+ expect(queryFn).toHaveBeenCalledTimes(0)
+ })
+
+ it('should not re-render when data is added to the cache when subscribed is false', async () => {
+ const key = queryKey()
+ let renders = 0
+ function Page() {
+ const { data } = useQuery({
+ queryKey: key,
+ queryFn: async () => 'data',
+ subscribed: false,
+ })
+ renders++
+ return (
+
+ {data ? 'has data' + data : 'no data'}
+
+
+ )
+ }
+
+ const rendered = renderWithClient(queryClient, )
+ await waitFor(() => rendered.getByText('no data'))
+
+ fireEvent.click(rendered.getByRole('button', { name: 'set data' }))
+
+ await sleep(10)
+
+ await waitFor(() => rendered.getByText('no data'))
+
+ expect(renders).toBe(1)
+ })
+ })
+
it('should have status=error on mount when a query has failed', async () => {
const key = queryKey()
const states: Array> = []
diff --git a/packages/react-query/src/types.ts b/packages/react-query/src/types.ts
index 9f6d7c315f..8bbb351a85 100644
--- a/packages/react-query/src/types.ts
+++ b/packages/react-query/src/types.ts
@@ -36,7 +36,13 @@ export interface UseBaseQueryOptions<
TData,
TQueryData,
TQueryKey
- > {}
+ > {
+ /**
+ * Set this to `false` to unsubscribe this observer from updates to the query cache.
+ * Defaults to `true`.
+ */
+ subscribed?: boolean
+}
export type AnyUseQueryOptions = UseQueryOptions
export interface UseQueryOptions<
diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts
index bcbf700ef7..1ee2f1cd07 100644
--- a/packages/react-query/src/useBaseQuery.ts
+++ b/packages/react-query/src/useBaseQuery.ts
@@ -82,14 +82,16 @@ export function useBaseQuery<
),
)
+ // note: this must be called before useSyncExternalStore
const result = observer.getOptimisticResult(defaultedOptions)
+ const shouldSubscribe = !isRestoring && options.subscribed !== false
React.useSyncExternalStore(
React.useCallback(
(onStoreChange) => {
- const unsubscribe = isRestoring
- ? noop
- : observer.subscribe(notifyManager.batchCalls(onStoreChange))
+ const unsubscribe = shouldSubscribe
+ ? observer.subscribe(notifyManager.batchCalls(onStoreChange))
+ : noop
// Update result to make sure we did not miss any query updates
// between creating the observer and subscribing to it.
@@ -97,7 +99,7 @@ export function useBaseQuery<
return unsubscribe
},
- [observer, isRestoring],
+ [observer, shouldSubscribe],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
diff --git a/packages/react-query/src/useQueries.ts b/packages/react-query/src/useQueries.ts
index 90ef2e32ad..dd4ac9f96e 100644
--- a/packages/react-query/src/useQueries.ts
+++ b/packages/react-query/src/useQueries.ts
@@ -47,7 +47,7 @@ type UseQueryOptionsForUseQueries<
TQueryKey extends QueryKey = QueryKey,
> = OmitKeyof<
UseQueryOptions,
- 'placeholderData'
+ 'placeholderData' | 'subscribed'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction
}
@@ -231,6 +231,7 @@ export function useQueries<
}: {
queries: readonly [...QueriesOptions]
combine?: (result: QueriesResults) => TCombinedResult
+ subscribed?: boolean
},
queryClient?: QueryClient,
): TCombinedResult {
@@ -271,19 +272,21 @@ export function useQueries<
),
)
+ // note: this must be called before useSyncExternalStore
const [optimisticResult, getCombinedResult, trackResult] =
observer.getOptimisticResult(
defaultedQueries,
(options as QueriesObserverOptions).combine,
)
+ const shouldSubscribe = !isRestoring && options.subscribed !== false
React.useSyncExternalStore(
React.useCallback(
(onStoreChange) =>
- isRestoring
- ? noop
- : observer.subscribe(notifyManager.batchCalls(onStoreChange)),
- [observer, isRestoring],
+ shouldSubscribe
+ ? observer.subscribe(notifyManager.batchCalls(onStoreChange))
+ : noop,
+ [observer, shouldSubscribe],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),