Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for skipToken in options for useSuspenseQuery and useBackgroundQuery and soft deprecates skip option #11112

Merged
merged 73 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
6456222
Create a skipToken constant
jerelmiller Jul 24, 2023
dac769e
Get working types for skipToken with useSuspenseQuery
jerelmiller Jul 27, 2023
7a3fa2a
Add comment explaining when condition occurs
jerelmiller Jul 27, 2023
fbee9e4
Remove unneeded bind in constructor
jerelmiller Jul 27, 2023
d88a870
Update comment about comparison
jerelmiller Jul 27, 2023
4afa483
Add comment explaining why getCurrentResult has false
jerelmiller Jul 27, 2023
7890d2f
Pass cache key inline to getQueryRef
jerelmiller Jul 27, 2023
8263ec0
Move call to suspense cache inline
jerelmiller Jul 27, 2023
5a14383
Pass undefined to useApolloClient when options is set to skip token
jerelmiller Jul 28, 2023
95e8c08
Don't use destructured values from options
jerelmiller Jul 28, 2023
4d7de45
Use options.variables for query ref key
jerelmiller Jul 28, 2023
b004d6a
Compute fetchPolicy in render
jerelmiller Jul 28, 2023
c1ced40
Remove client from useWatchQueryOptions
jerelmiller Jul 28, 2023
ae6ccfd
Move validation of options to custom hook
jerelmiller Jul 28, 2023
cfb1463
Update function signature of validateOptions
jerelmiller Jul 28, 2023
6af7cde
Create watchQueryOptions in render
jerelmiller Jul 28, 2023
66196ba
Move watch query option overrides to constant
jerelmiller Jul 28, 2023
6aea24d
Move query in all spreads
jerelmiller Jul 28, 2023
6352f5a
Refactor to options everywhere
jerelmiller Jul 28, 2023
000a8c3
No need to pass query when checking if options changed
jerelmiller Jul 28, 2023
4b1a606
Allow skiptoken in validation
jerelmiller Jul 28, 2023
4d57e2b
Handle skip token set as options
jerelmiller Jul 28, 2023
aa24879
Add test to validate using skip token after already run maintains data
jerelmiller Jul 28, 2023
9a73240
Simplify test with skipToken
jerelmiller Jul 28, 2023
ee16166
Add missing skipToken import
jerelmiller Jul 28, 2023
0fdaef1
Abstract a watchQueryOptions variable
jerelmiller Jul 28, 2023
e887a17
Add additional tests for skipToken with options
jerelmiller Jul 28, 2023
ad7d364
Remove unused import
jerelmiller Jul 28, 2023
a9280aa
Use a fulfilled promise for the skip result
jerelmiller Jul 28, 2023
604eef2
Extract skip result logic to function
jerelmiller Jul 28, 2023
c4d3c9c
Handle undefined result
jerelmiller Jul 28, 2023
1501ded
Remove support for `skipToken` as the `query` to `useSuspenseQuery`
jerelmiller Jul 28, 2023
0948cf0
Consolidate validateOptions into useValidateOptions and remove SkipTo…
jerelmiller Jul 28, 2023
f82c4dc
Create an internal useValidateSuspenseHookOptions hook
jerelmiller Jul 28, 2023
9591153
Add test that validates supported fetchPolicy from global options
jerelmiller Jul 28, 2023
5fdfbab
Don't publicly export refetch/fetchmore/subscribetoMore function types
jerelmiller Jul 28, 2023
0fddf36
Restore implementation to pre-refactor stage and get it working with …
jerelmiller Jul 28, 2023
96612ed
Fix import of RefetchFunction and FetchMoreFunction
jerelmiller Jul 28, 2023
a5a11f3
Run prettier on useBackgroundQuery test file
jerelmiller Jul 28, 2023
874c7d6
Remove useValidateSuspenseHookOptions
jerelmiller Jul 28, 2023
b4b54f6
Update return type of useBackgroundQuery test
jerelmiller Jul 28, 2023
5a90f53
No need for React.useMemo on returned tuple from useBackgroundQuery
jerelmiller Jul 28, 2023
28fff6a
Return undefined from useBackgroundQuery when skipping for the first …
jerelmiller Jul 28, 2023
2640b52
Allow prettier to format useReadQuery
jerelmiller Jul 28, 2023
6de296e
Remove unneeded skipResult check in useReadQuery
jerelmiller Jul 28, 2023
5e3e46e
Add missing TVariables in useBackgroundQuery hook
jerelmiller Jul 28, 2023
2383535
WIP add support for skip token in useBackgroundQuery
jerelmiller Jul 28, 2023
f5cdbef
Add additional type test to ensure undefined options are set correctly
jerelmiller Jul 29, 2023
85a49f2
Fix type when using skip token with/without returnPartialData
jerelmiller Jul 29, 2023
84038f2
Remove unused import
jerelmiller Jul 29, 2023
0acae4c
Use helper to wrap/unwrap query ref
jerelmiller Jul 29, 2023
3a01bed
Add additional overload to handle constant skip token
jerelmiller Jul 29, 2023
b935463
Add additional skip tests when using `skipToken`
jerelmiller Jul 29, 2023
94938bf
Add deprecated tag to skip option in types
jerelmiller Jul 29, 2023
10e7eb0
Add changeset
jerelmiller Jul 29, 2023
2cdaf1d
Merge branch 'release-3.8' into skip-token
jerelmiller Jul 29, 2023
1bd90c7
Better comment about tracking skip state
jerelmiller Jul 29, 2023
d6b9637
Import invariant from globals/index in useReadQuery
jerelmiller Jul 29, 2023
b9a512b
Fix reference to wrong value in useReadQuery useEffect
jerelmiller Jul 29, 2023
9c4930f
useSyncExternalStore to update promise cache in useReadQuery
jerelmiller Jul 29, 2023
ed10a80
Add additional comment about removal of skip option in changeset
jerelmiller Jul 31, 2023
bf20335
Use `Symbol.for` with `skipToken` and update to use apollo.* namespace
jerelmiller Jul 31, 2023
57dee8e
Use type export for `SkipToken` type
jerelmiller Jul 31, 2023
a6529c6
Use type condition with never to handle union with undefined when ski…
jerelmiller Jul 31, 2023
761f6bd
Use Partial on internal hook signature to avoid additional code to sa…
jerelmiller Jul 31, 2023
1e31b27
Add code example to deprecated `skip` option tsdoc
jerelmiller Jul 31, 2023
99c2881
Use destructured promiseCache when setting key
jerelmiller Aug 1, 2023
c64b15f
Return `undefined` type when using unconditionl `skipToken` for `useB…
jerelmiller Aug 1, 2023
1e5a844
Add checks to skip tests to ensure text isn't hidden because its susp…
jerelmiller Aug 1, 2023
6c861b2
Better test description for skip token test
jerelmiller Aug 1, 2023
2f6cc8b
Update test for swapping skip back and forth to change variables to e…
jerelmiller Aug 1, 2023
35a0240
Merge remote-tracking branch 'origin/release-3.8' into skip-token
jerelmiller Aug 1, 2023
569df84
useMemo the wrapped query ref
jerelmiller Aug 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .changeset/early-days-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
'@apollo/client': minor
---

Adds support for a `skipToken` sentinel that can be used as `options` in `useSuspenseQuery` and `useBackgroundQuery` to skip execution of a query. This works identically to the `skip` option but is more type-safe and as such, becomes the recommended way to skip query execution. As such, the `skip` option has been deprecated in favor of `skipToken`.
jerelmiller marked this conversation as resolved.
Show resolved Hide resolved

```ts
import { skipToken } from '@apollo/client';

const id: number | undefined;

const { data } = useSuspenseQuery(
query,
id ? { variables: { id } } : skipToken
);
```

### Breaking change

Previously `useBackgroundQuery` would always return a `queryRef` whenever query execution was skipped. This behavior been updated to return a `queryRef` only when query execution is enabled. If initializing the hook with it skipped, `queryRef` is now returned as `undefined`.

To migrate, conditionally render the component that accepts the `queryRef` as props.

**Before**
```ts
function Parent() {
const [queryRef] = useBackgroundQuery(query, skip ? skipToken : undefined);
// ^? QueryReference<TData | undefined>

return <Child queryRef={queryRef} />
}

function Child({ queryRef }: { queryRef: QueryReference<TData | undefined> }) {
const { data } = useReadQuery(queryRef);
}
```

**After**
```ts
function Parent() {
const [queryRef] = useBackgroundQuery(query, skip ? skipToken : undefined);
// ^? QueryReference<TData> | undefined

return queryRef ? <Child queryRef={queryRef} /> : null;
}

function Child({ queryRef }: { queryRef: QueryReference<TData> }) {
const { data } = useReadQuery(queryRef);
}
```
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ src/react/hooks/*
!src/react/hooks/useSuspenseCache.ts
!src/react/hooks/useSuspenseQuery.ts
!src/react/hooks/useBackgroundQuery.ts
!src/react/hooks/useReadQuery.ts
!src/react/hooks/constants.ts

## Allowed React hook tests
!src/react/hooks/__tests__/
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Array [
"selectURI",
"serializeFetchParameter",
"setLogVerbosity",
"skipToken",
"split",
"throwServerError",
"toPromise",
Expand Down Expand Up @@ -270,6 +271,7 @@ Array [
"operationName",
"parser",
"resetApolloContext",
"skipToken",
"useApolloClient",
"useBackgroundQuery",
"useFragment",
Expand Down Expand Up @@ -312,6 +314,7 @@ Array [

exports[`exports of public entry points @apollo/client/react/hooks 1`] = `
Array [
"skipToken",
"useApolloClient",
"useBackgroundQuery",
"useFragment",
Expand Down
34 changes: 27 additions & 7 deletions src/react/cache/QueryReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type FetchMoreOptions<TData> = Parameters<
ObservableQuery<TData>['fetchMore']
>[0];

export const QUERY_REFERENCE_SYMBOL: unique symbol = Symbol();
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
Expand All @@ -37,14 +37,31 @@ interface InternalQueryReferenceOptions {
autoDisposeTimeoutMs?: number;
}

const OBSERVED_CHANGED_OPTIONS: Array<keyof WatchQueryOptions> = [
export function wrapQueryRef<TData>(
internalQueryRef: InternalQueryReference<TData>
): QueryReference<TData> {
return { [QUERY_REFERENCE_SYMBOL]: internalQueryRef };
}

export function unwrapQueryRef<TData>(
queryRef: QueryReference<TData>
): InternalQueryReference<TData> {
return queryRef[QUERY_REFERENCE_SYMBOL];
}

const OBSERVED_CHANGED_OPTIONS = [
'canonizeResults',
'context',
'errorPolicy',
'fetchPolicy',
'refetchWritePolicy',
'returnPartialData',
];
] as const;

type ObservedOptions = Pick<
WatchQueryOptions,
typeof OBSERVED_CHANGED_OPTIONS[number]
>;

export class InternalQueryReference<TData = unknown> {
public result: ApolloQueryResult<TData>;
Expand All @@ -68,12 +85,12 @@ export class InternalQueryReference<TData = unknown> {
observable: ObservableQuery<TData>,
options: InternalQueryReferenceOptions
) {
this.listen = this.listen.bind(this);
this.handleNext = this.handleNext.bind(this);
this.handleError = this.handleError.bind(this);
this.initiateFetch = this.initiateFetch.bind(this);
this.dispose = this.dispose.bind(this);
this.observable = observable;
// Don't save this result as last result to prevent delivery of last result
// when first subscribing
this.result = observable.getCurrentResult(false);
this.key = options.key;

Expand Down Expand Up @@ -138,14 +155,14 @@ export class InternalQueryReference<TData = unknown> {
};
}

didChangeOptions(watchQueryOptions: WatchQueryOptions) {
didChangeOptions(watchQueryOptions: ObservedOptions) {
return OBSERVED_CHANGED_OPTIONS.some(
(option) =>
!equal(this.watchQueryOptions[option], watchQueryOptions[option])
);
}

applyOptions(watchQueryOptions: WatchQueryOptions) {
applyOptions(watchQueryOptions: ObservedOptions) {
const {
fetchPolicy: currentFetchPolicy,
canonizeResults: currentCanonizeResults,
Expand Down Expand Up @@ -209,6 +226,9 @@ export class InternalQueryReference<TData = unknown> {
break;
}
case 'idle': {
// This occurs when switching to a result that is fully cached when this
// class is instantiated. ObservableQuery will run reobserve when
// subscribing, which delivers a result from the cache.
if (result.data === this.result.data) {
return;
}
Expand Down
Loading