diff --git a/.changeset/smooth-clouds-sort.md b/.changeset/smooth-clouds-sort.md new file mode 100644 index 00000000000..292eb6b0122 --- /dev/null +++ b/.changeset/smooth-clouds-sort.md @@ -0,0 +1,5 @@ +--- +'@apollo/client': patch +--- + +Hide queryRef in a Symbol in `useBackgroundQuery`s return value. diff --git a/.size-limit.cjs b/.size-limit.cjs index 9200ba8d3b8..72ccf4c9705 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,7 +1,7 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "37700" + limit: "37750" }, { path: "dist/main.cjs", @@ -10,7 +10,7 @@ const checks = [ { path: "dist/index.js", import: "{ ApolloClient, InMemoryCache, HttpLink }", - limit: "33269" + limit: "33375" }, ...[ "ApolloProvider", diff --git a/src/react/cache/QueryReference.ts b/src/react/cache/QueryReference.ts index 7f42e4013d5..d593198136f 100644 --- a/src/react/cache/QueryReference.ts +++ b/src/react/cache/QueryReference.ts @@ -9,6 +9,7 @@ import { NetworkStatus, isNetworkRequestSettled } from '../../core'; import type { ObservableSubscription } from '../../utilities'; import { createFulfilledPromise, createRejectedPromise } from '../../utilities'; import type { CacheKey } from './types'; +import type { useBackgroundQuery, useReadQuery } from '../hooks'; type Listener = (promise: Promise>) => void; @@ -16,13 +17,23 @@ type FetchMoreOptions = Parameters< ObservableQuery['fetchMore'] >[0]; -interface QueryReferenceOptions { +export const QUERY_REFERENCE_SYMBOL: unique symbol = Symbol(); +/** + * A `QueryReference` is an opaque object returned by {@link useBackgroundQuery}. + * A child component reading the `QueryReference` via {@link useReadQuery} will + * suspend until the promise resolves. + */ +export interface QueryReference { + [QUERY_REFERENCE_SYMBOL]: InternalQueryReference; +} + +interface InternalQueryReferenceOptions { key: CacheKey; onDispose?: () => void; autoDisposeTimeoutMs?: number; } -export class QueryReference { +export class InternalQueryReference { public result: ApolloQueryResult; public readonly key: CacheKey; public readonly observable: ObservableQuery; @@ -41,7 +52,7 @@ export class QueryReference { constructor( observable: ObservableQuery, - options: QueryReferenceOptions + options: InternalQueryReferenceOptions ) { this.listen = this.listen.bind(this); this.handleNext = this.handleNext.bind(this); diff --git a/src/react/cache/SuspenseCache.ts b/src/react/cache/SuspenseCache.ts index d7701d989f7..89ecf6ac300 100644 --- a/src/react/cache/SuspenseCache.ts +++ b/src/react/cache/SuspenseCache.ts @@ -1,7 +1,7 @@ import { Trie } from '@wry/trie'; import type { ObservableQuery } from '../../core'; import { canUseWeakMap } from '../../utilities'; -import { QueryReference } from './QueryReference'; +import { InternalQueryReference } from './QueryReference'; import type { CacheKey } from './types'; interface SuspenseCacheOptions { @@ -19,7 +19,7 @@ interface SuspenseCacheOptions { } export class SuspenseCache { - private queryRefs = new Trie<{ current?: QueryReference }>(canUseWeakMap); + private queryRefs = new Trie<{ current?: InternalQueryReference }>(canUseWeakMap); private options: SuspenseCacheOptions; constructor(options: SuspenseCacheOptions = Object.create(null)) { @@ -33,7 +33,7 @@ export class SuspenseCache { const ref = this.queryRefs.lookupArray(cacheKey); if (!ref.current) { - ref.current = new QueryReference(createObservable(), { + ref.current = new InternalQueryReference(createObservable(), { key: cacheKey, autoDisposeTimeoutMs: this.options.autoDisposeTimeoutMs, onDispose: () => { @@ -42,6 +42,6 @@ export class SuspenseCache { }); } - return ref.current as QueryReference; + return ref.current as InternalQueryReference; } } diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index c57832c04eb..be29bd3bb48 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -37,6 +37,7 @@ import { import { concatPagination, offsetLimitPagination } from '../../../utilities'; import { useBackgroundQuery, useReadQuery } from '../useBackgroundQuery'; import { ApolloProvider } from '../../context'; +import { QUERY_REFERENCE_SYMBOL } from '../../cache/QueryReference'; import { SuspenseCache } from '../../cache'; import { InMemoryCache } from '../../../cache'; import { @@ -617,7 +618,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { hello: 'world 1' }, @@ -654,7 +655,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; await waitFor(() => { expect(_result).toEqual({ @@ -739,7 +740,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; await waitFor(() => { expect(_result).toMatchObject({ @@ -803,7 +804,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; const resultSet = new Set(_result.data.results); const values = Array.from(resultSet).map((item) => item.value); @@ -868,7 +869,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; const resultSet = new Set(_result.data.results); const values = Array.from(resultSet).map((item) => item.value); @@ -913,7 +914,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { hello: 'from link' }, @@ -956,7 +957,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { hello: 'from cache' }, @@ -1006,7 +1007,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { foo: 'bar', hello: 'from link' }, @@ -1049,7 +1050,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { hello: 'from link' }, @@ -1095,7 +1096,7 @@ describe('useBackgroundQuery', () => { const [queryRef] = result.current; - const _result = await queryRef.promise; + const _result = await queryRef[QUERY_REFERENCE_SYMBOL].promise; expect(_result).toEqual({ data: { hello: 'from link' }, diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index ebd3dfcc19e..42e9ba384c3 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -6,7 +6,10 @@ import type { } from '../../core'; import { NetworkStatus } from '../../core'; import { useApolloClient } from './useApolloClient'; -import type { QueryReference } from '../cache/QueryReference'; +import { + QUERY_REFERENCE_SYMBOL, + type QueryReference, +} from '../cache/QueryReference'; import type { SuspenseQueryHookOptions, NoInfer } from '../types/types'; import { __use } from './internal'; import { useSuspenseCache } from './useSuspenseCache'; @@ -188,7 +191,7 @@ export function useBackgroundQuery< return useMemo(() => { return [ - queryRef, + { [QUERY_REFERENCE_SYMBOL]: queryRef }, { fetchMore, refetch, @@ -199,41 +202,41 @@ export function useBackgroundQuery< export function useReadQuery(queryRef: QueryReference) { const [, forceUpdate] = useState(0); - + const internalQueryRef = queryRef[QUERY_REFERENCE_SYMBOL]; invariant( - queryRef.promiseCache, + internalQueryRef.promiseCache, 'It appears that `useReadQuery` was used outside of `useBackgroundQuery`. ' + '`useReadQuery` is only supported for use with `useBackgroundQuery`. ' + 'Please ensure you are passing the `queryRef` returned from `useBackgroundQuery`.' ); const skipResult = useMemo(() => { - const error = toApolloError(queryRef.result); + const error = toApolloError(internalQueryRef.result); return { loading: false, - data: queryRef.result.data, + data: internalQueryRef.result.data, networkStatus: error ? NetworkStatus.error : NetworkStatus.ready, error, }; - }, [queryRef.result]); + }, [internalQueryRef.result]); - let promise = queryRef.promiseCache.get(queryRef.key); + let promise = internalQueryRef.promiseCache.get(internalQueryRef.key); if (!promise) { - promise = queryRef.promise; - queryRef.promiseCache.set(queryRef.key, promise); + promise = internalQueryRef.promise; + internalQueryRef.promiseCache.set(internalQueryRef.key, promise); } useEffect(() => { - return queryRef.listen((promise) => { - queryRef.promiseCache!.set(queryRef.key, promise); + return internalQueryRef.listen((promise) => { + internalQueryRef.promiseCache!.set(internalQueryRef.key, promise); forceUpdate((prevState) => prevState + 1); }); }, [queryRef]); const result = - queryRef.watchQueryOptions.fetchPolicy === 'standby' + internalQueryRef.watchQueryOptions.fetchPolicy === 'standby' ? skipResult : __use(promise); diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index 2d8d4d7659d..62be53ddf43 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -22,7 +22,7 @@ import type { } from '../types/types'; import { useDeepMemo, useStrictModeSafeCleanupEffect, __use } from './internal'; import { useSuspenseCache } from './useSuspenseCache'; -import type { QueryReference } from '../cache/QueryReference'; +import type { InternalQueryReference } from '../cache/QueryReference'; import { canonicalStringify } from '../../cache'; export interface UseSuspenseQueryResult< @@ -296,8 +296,8 @@ export function toApolloError(result: ApolloQueryResult) { : result.error; } -export function useTrackedQueryRefs(queryRef: QueryReference) { - const trackedQueryRefs = useRef(new Set()); +export function useTrackedQueryRefs(queryRef: InternalQueryReference) { + const trackedQueryRefs = useRef(new Set()); trackedQueryRefs.current.add(queryRef);