Skip to content

Commit 27d766e

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents a9ae023 + 7af572f commit 27d766e

File tree

4 files changed

+125
-10
lines changed

4 files changed

+125
-10
lines changed

packages/react-query/src/__tests__/useQuery.test.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5964,6 +5964,110 @@ describe('useQuery', () => {
59645964
})
59655965
})
59665966

5967+
describe('subscribed', () => {
5968+
it('should be able to toggle subscribed', async () => {
5969+
const key = queryKey()
5970+
const queryFn = vi.fn(async () => 'data')
5971+
function Page() {
5972+
const [subscribed, setSubscribed] = React.useState(true)
5973+
const { data } = useQuery({
5974+
queryKey: key,
5975+
queryFn,
5976+
subscribed,
5977+
})
5978+
return (
5979+
<div>
5980+
<span>data: {data}</span>
5981+
<button onClick={() => setSubscribed(!subscribed)}>toggle</button>
5982+
</div>
5983+
)
5984+
}
5985+
5986+
const rendered = renderWithClient(queryClient, <Page />)
5987+
await waitFor(() => rendered.getByText('data: data'))
5988+
5989+
expect(
5990+
queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
5991+
).toBe(1)
5992+
5993+
fireEvent.click(rendered.getByRole('button', { name: 'toggle' }))
5994+
5995+
expect(
5996+
queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
5997+
).toBe(0)
5998+
5999+
expect(queryFn).toHaveBeenCalledTimes(1)
6000+
6001+
fireEvent.click(rendered.getByRole('button', { name: 'toggle' }))
6002+
6003+
// background refetch when we re-subscribe
6004+
await waitFor(() => expect(queryFn).toHaveBeenCalledTimes(2))
6005+
expect(
6006+
queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
6007+
).toBe(1)
6008+
})
6009+
6010+
it('should not be attached to the query when subscribed is false', async () => {
6011+
const key = queryKey()
6012+
const queryFn = vi.fn(async () => 'data')
6013+
function Page() {
6014+
const { data } = useQuery({
6015+
queryKey: key,
6016+
queryFn,
6017+
subscribed: false,
6018+
})
6019+
return (
6020+
<div>
6021+
<span>data: {data}</span>
6022+
</div>
6023+
)
6024+
}
6025+
6026+
const rendered = renderWithClient(queryClient, <Page />)
6027+
await waitFor(() => rendered.getByText('data:'))
6028+
6029+
expect(
6030+
queryClient.getQueryCache().find({ queryKey: key })!.observers.length,
6031+
).toBe(0)
6032+
6033+
expect(queryFn).toHaveBeenCalledTimes(0)
6034+
})
6035+
6036+
it('should not re-render when data is added to the cache when subscribed is false', async () => {
6037+
const key = queryKey()
6038+
let renders = 0
6039+
function Page() {
6040+
const { data } = useQuery({
6041+
queryKey: key,
6042+
queryFn: async () => 'data',
6043+
subscribed: false,
6044+
})
6045+
renders++
6046+
return (
6047+
<div>
6048+
<span>{data ? 'has data' + data : 'no data'}</span>
6049+
<button
6050+
onClick={() => queryClient.setQueryData<string>(key, 'new data')}
6051+
>
6052+
set data
6053+
</button>
6054+
</div>
6055+
)
6056+
}
6057+
6058+
const rendered = renderWithClient(queryClient, <Page />)
6059+
await waitFor(() => rendered.getByText('no data'))
6060+
6061+
fireEvent.click(rendered.getByRole('button', { name: 'set data' }))
6062+
6063+
await sleep(10)
6064+
6065+
await waitFor(() => rendered.getByText('no data'))
6066+
6067+
expect(renders).toBe(1)
6068+
})
6069+
})
6070+
59676071
it('should have status=error on mount when a query has failed', async () => {
59686072
const key = queryKey()
59696073
const states: Array<UseQueryResult<unknown>> = []

packages/react-query/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ export interface UseBaseQueryOptions<
3636
TData,
3737
TQueryData,
3838
TQueryKey
39-
> {}
39+
> {
40+
/**
41+
* Set this to `false` to unsubscribe this observer from updates to the query cache.
42+
* Defaults to `true`.
43+
*/
44+
subscribed?: boolean
45+
}
4046

4147
export type AnyUseQueryOptions = UseQueryOptions<any, any, any, any>
4248
export interface UseQueryOptions<

packages/react-query/src/useBaseQuery.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,24 @@ export function useBaseQuery<
8282
),
8383
)
8484

85+
// note: this must be called before useSyncExternalStore
8586
const result = observer.getOptimisticResult(defaultedOptions)
8687

88+
const shouldSubscribe = !isRestoring && options.subscribed !== false
8789
React.useSyncExternalStore(
8890
React.useCallback(
8991
(onStoreChange) => {
90-
const unsubscribe = isRestoring
91-
? noop
92-
: observer.subscribe(notifyManager.batchCalls(onStoreChange))
92+
const unsubscribe = shouldSubscribe
93+
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
94+
: noop
9395

9496
// Update result to make sure we did not miss any query updates
9597
// between creating the observer and subscribing to it.
9698
observer.updateResult()
9799

98100
return unsubscribe
99101
},
100-
[observer, isRestoring],
102+
[observer, shouldSubscribe],
101103
),
102104
() => observer.getCurrentResult(),
103105
() => observer.getCurrentResult(),

packages/react-query/src/useQueries.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type UseQueryOptionsForUseQueries<
4747
TQueryKey extends QueryKey = QueryKey,
4848
> = OmitKeyof<
4949
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
50-
'placeholderData'
50+
'placeholderData' | 'subscribed'
5151
> & {
5252
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
5353
}
@@ -231,6 +231,7 @@ export function useQueries<
231231
}: {
232232
queries: readonly [...QueriesOptions<T>]
233233
combine?: (result: QueriesResults<T>) => TCombinedResult
234+
subscribed?: boolean
234235
},
235236
queryClient?: QueryClient,
236237
): TCombinedResult {
@@ -271,19 +272,21 @@ export function useQueries<
271272
),
272273
)
273274

275+
// note: this must be called before useSyncExternalStore
274276
const [optimisticResult, getCombinedResult, trackResult] =
275277
observer.getOptimisticResult(
276278
defaultedQueries,
277279
(options as QueriesObserverOptions<TCombinedResult>).combine,
278280
)
279281

282+
const shouldSubscribe = !isRestoring && options.subscribed !== false
280283
React.useSyncExternalStore(
281284
React.useCallback(
282285
(onStoreChange) =>
283-
isRestoring
284-
? noop
285-
: observer.subscribe(notifyManager.batchCalls(onStoreChange)),
286-
[observer, isRestoring],
286+
shouldSubscribe
287+
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
288+
: noop,
289+
[observer, shouldSubscribe],
287290
),
288291
() => observer.getCurrentResult(),
289292
() => observer.getCurrentResult(),

0 commit comments

Comments
 (0)