Skip to content

Commit 71f2517

Browse files
authored
Support skipToken with useQuery (#12895)
1 parent 5352c12 commit 71f2517

File tree

6 files changed

+1068
-58
lines changed

6 files changed

+1068
-58
lines changed

.api-reports/api-report-react.api.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,16 +681,34 @@ export function useQuery<TData = unknown, TVariables extends OperationVariables
681681
returnPartialData: true;
682682
}): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming" | "partial">;
683683

684+
// @public
685+
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, options: SkipToken): useQuery.Result<TData, TVariables, "empty", Record<string, never>>;
686+
687+
// @public
688+
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, options: SkipToken | (useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>> & {
689+
returnPartialData: true;
690+
})): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming" | "partial", Partial<TVariables>>;
691+
684692
// @public
685693
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, options: useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>> & {
686694
returnPartialData: boolean;
687695
}): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming" | "partial">;
688696

697+
// @public
698+
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, options: SkipToken | (useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>> & {
699+
returnPartialData: boolean;
700+
})): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming" | "partial", Partial<TVariables>>;
701+
689702
// @public
690703
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, ...[options]: {} extends TVariables ? [
691704
options?: useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>>
692705
] : [options: useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>>]): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming">;
693706

707+
// @public
708+
export function useQuery<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode_2 | TypedDocumentNode_2<TData, TVariables>, ...[options]: {} extends TVariables ? [
709+
options?: SkipToken | useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>>
710+
] : [options: SkipToken | useQuery.Options<NoInfer_2<TData>, NoInfer_2<TVariables>>]): useQuery.Result<TData, TVariables, "empty" | "complete" | "streaming", Partial<TVariables>>;
711+
694712
// @public (undocumented)
695713
export namespace useQuery {
696714
// (undocumented)
@@ -715,7 +733,7 @@ export namespace useQuery {
715733
// (undocumented)
716734
export namespace Base {
717735
// (undocumented)
718-
export interface Result<TData = unknown, TVariables extends OperationVariables = OperationVariables> {
736+
export interface Result<TData = unknown, TVariables extends OperationVariables = OperationVariables, TReturnVariables extends OperationVariables = TVariables> {
719737
client: ApolloClient;
720738
error?: ErrorLike;
721739
fetchMore: <TFetchData = TData, TFetchVars extends OperationVariables = TVariables>(fetchMoreOptions: ObservableQuery.FetchMoreOptions<TData, TVariables, TFetchData, TFetchVars>) => Promise<ApolloClient.QueryResult<MaybeMasked_2<TFetchData>>>;
@@ -728,7 +746,7 @@ export namespace useQuery {
728746
stopPolling: () => void;
729747
subscribeToMore: SubscribeToMoreFunction<TData, TVariables>;
730748
updateQuery: (mapFn: UpdateQueryMapFn<TData, TVariables>) => void;
731-
variables: TVariables;
749+
variables: TReturnVariables;
732750
}
733751
}
734752
// (undocumented)
@@ -756,7 +774,7 @@ export namespace useQuery {
756774
// (undocumented)
757775
export type Options<TData = unknown, TVariables extends OperationVariables = OperationVariables> = Base.Options<TData, TVariables> & VariablesOption<TVariables>;
758776
// (undocumented)
759-
export type Result<TData = unknown, TVariables extends OperationVariables = OperationVariables, TStates extends DataState<TData>["dataState"] = DataState<TData>["dataState"]> = Base.Result<TData, TVariables> & GetDataState<MaybeMasked_2<TData>, TStates>;
777+
export type Result<TData = unknown, TVariables extends OperationVariables = OperationVariables, TStates extends DataState<TData>["dataState"] = DataState<TData>["dataState"], TReturnVariables extends OperationVariables = TVariables> = Base.Result<TData, TVariables, TReturnVariables> & GetDataState<MaybeMasked_2<TData>, TStates>;
760778
}
761779

762780
// @public (undocumented)

.changeset/tame-grapes-kiss.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Support `skipToken` with `useQuery` to provide a more type-safe way to skip query execution.
6+
7+
```ts
8+
import { skipToken, useQuery } from "@apollo/client/react"
9+
10+
// Use `skipToken` in place of `skip: true` for better type safety
11+
// for required variables
12+
const { data } = useQuery(QUERY, id ? { variables: { id } } : skipToken);
13+
```
14+
15+
Note: this change is provided as a patch within the 4.0 minor version because the changes to TypeScript validation with required variables in version 4.0 made using the `skip` option more difficult.

docs/source/api/react/skipToken.mdx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ description: Apollo Client API reference
77

88
## `skipToken`
99

10-
`skipToken` provides a type-safe mechanism to skip query execution. It is currently supported with `useSuspenseQuery` and `useBackgroundQuery`.
10+
`skipToken` provides a type-safe mechanism to skip query execution. It is currently supported with `useQuery`, `useSuspenseQuery` and `useBackgroundQuery`.
1111
When you pass a `skipToken` to one of the supported hooks instead of the `options` object, the hook will not cause any requests or suspenseful behavior and keeps the last `data` available. It is typically used conditionally to start query execution when the input data is available.
1212

13+
```js title="Recommended usage of skipToken with useQuery"
14+
import { skipToken, useQuery } from "@apollo/client/react";
15+
16+
const { data } = useQuery(query, id ? { variables: { id } } : skipToken);
17+
```
18+
1319
```js title="Recommended usage of skipToken with useSuspenseQuery"
1420
import { skipToken, useSuspenseQuery } from "@apollo/client/react";
1521
const { data } = useSuspenseQuery(
@@ -26,9 +32,7 @@ const [queryRef] = useBackgroundQuery(
2632
);
2733
```
2834

29-
<blockquote>
30-
31-
Note: **Why do we recommend `skipToken` over `{ skip: true }`?**
35+
### Why do we recommend `skipToken` over `{ skip: true }`?
3236

3337
Imagine this very common scenario for `skip`: You want to skip your query if a certain required variable is not set. You might be tempted to write something like this:
3438

@@ -76,5 +80,3 @@ const { data } = useSuspenseQuery(
7680
```
7781

7882
Here it becomes apparent for TypeScript that there is a direct connection between skipping and the `variables` option - and it will work without unsafe workarounds.
79-
80-
</blockquote>

docs/source/data/queries.mdx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,64 @@ Uses the same logic as `cache-first`, except this query does _not_ automatically
799799
</tbody>
800800
</table>
801801
802+
<MinVersion version="4.0.4">
803+
804+
## Skipping queries with `skipToken`
805+
806+
</MinVersion>
807+
808+
`skipToken` provides a type-safe mechanism to skip query execution. When you pass `skipToken` to `useQuery` instead of the options object, the hook will not execute the query and keeps the last `data` available. It is typically used conditionally to start query execution when the input data is available.
809+
810+
```js title="Recommended usage of skipToken with useQuery"
811+
import { skipToken, useQuery } from "@apollo/client/react";
812+
813+
const { data } = useQuery(query, id ? { variables: { id } } : skipToken);
814+
```
815+
816+
Imagine this common scenario: You want to skip your query if a certain required variable is not set. You might be tempted to write something like this:
817+
818+
```ts
819+
const { data } = useQuery(query, {
820+
variables: { id },
821+
skip: !id,
822+
});
823+
```
824+
825+
But in that case, TypeScript will complain:
826+
827+
```
828+
Type 'number | undefined' is not assignable to type 'number'.
829+
Type 'undefined' is not assignable to type 'number'.ts(2769)
830+
```
831+
832+
To get around that, you have to tell TypeScript to ignore the fact that `id` could be `undefined`:
833+
834+
```ts
835+
const { data } = useQuery(query, {
836+
variables: { id: id! },
837+
skip: !id,
838+
});
839+
```
840+
841+
Alternatively, you could also use some obscure default value:
842+
843+
```ts
844+
const { data } = useQuery(query, {
845+
variables: { id: id || 0 },
846+
skip: !id,
847+
});
848+
```
849+
850+
Both of these solutions hide a potential bug. If your `skip` logic becomes more complex, you might accidentally introduce a bug that causes your query to execute, even when `id` is still `undefined`. In that case, TypeScript cannot warn you about it.
851+
852+
Instead we recommend using `skipToken`. It provides type safety without the need for an obscure default value:
853+
854+
```ts
855+
const { data } = useQuery(query, id ? { variables: { id } } : skipToken);
856+
```
857+
858+
Here it becomes apparent for TypeScript that there is a direct connection between skipping and the `variables` option - and it will work without unsafe workarounds.
859+
802860
## `useQuery` API
803861
804862
Supported options and result fields for the `useQuery` hook are listed below.

0 commit comments

Comments
 (0)