From cdc82ea2376f7ef23875896f12b43ae368b08a27 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 22 Sep 2023 14:18:52 +0200 Subject: [PATCH 1/4] useQuery: `variables/options` should only be required if `TVariables` is empty or default --- src/react/hooks/__tests__/useQuery.test.tsx | 112 ++++++++++++++++++++ src/react/hooks/useQuery.ts | 31 ++++++ 2 files changed, 143 insertions(+) diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index d900a2d53fe..81b5f0a9206 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -8236,4 +8236,116 @@ describe.skip("Type Tests", () => { // @ts-expect-error variables?.nonExistingVariable; }); + + describe("optional/required options/variables scenarios", () => { + test("untyped document node, variables are optional and can be anything", () => { + const query = {} as DocumentNode; + useQuery(query); + useQuery(query, {}); + useQuery(query, { variables: {} }); + useQuery(query, { variables: { opt: "opt" } }); + useQuery(query, { variables: { req: "req" } }); + }); + test("typed document node with unspecified TVariables, variables are optional and can be anything", () => { + const query = {} as TypedDocumentNode<{ result: string }>; + useQuery(query); + useQuery(query, {}); + useQuery(query, { variables: {} }); + useQuery(query, { variables: { opt: "opt" } }); + useQuery(query, { variables: { req: "req" } }); + }); + test("empty variables are optional", () => { + const query = {} as TypedDocumentNode< + { result: string }, + Record + >; + useQuery(query); + useQuery(query, {}); + useQuery(query, { variables: {} }); + useQuery(query, { + variables: { + // @ts-expect-error on unknown variable + foo: "bar", + }, + }); + }); + test("all-optional variables are optional", () => { + const query = {} as TypedDocumentNode< + { result: string }, + { opt?: string } + >; + useQuery(query); + useQuery(query, {}); + useQuery(query, { variables: {} }); + useQuery(query, { variables: { opt: "opt" } }); + useQuery(query, { + variables: { + // @ts-expect-error on unknown variable + foo: "bar", + }, + }); + useQuery(query, { + variables: { + opt: "opt", + // @ts-expect-error on unknown variable + foo: "bar", + }, + }); + }); + test("non-optional variables are required", () => { + const query = {} as TypedDocumentNode< + { result: string }, + { req: string } + >; + // @ts-expect-error on missing options + useQuery(query); + useQuery( + query, + // @ts-expect-error on missing variables + {} + ); + useQuery(query, { + // @ts-expect-error on empty variables + variables: {}, + }); + useQuery(query, { + variables: { + // @ts-expect-error on unknown variable + foo: "bar", + }, + }); + useQuery(query, { variables: { req: "req" } }); + useQuery(query, { + variables: { + req: "req", + // @ts-expect-error on unknown variable + foo: "bar", + }, + }); + }); + test("mixed variables are required", () => { + const query = {} as TypedDocumentNode< + { result: string }, + { req: string; opt?: string } + >; + // @ts-expect-error on missing options + useQuery(query); + // @ts-expect-error on missing variables + useQuery(query, {}); + + useQuery(query, { + // @ts-expect-error on empty variables + variables: {}, + }); + + useQuery(query, { + // @ts-expect-error on missing required variable + variables: { + opt: "opt", + }, + }); + useQuery(query, { variables: { req: "req" } }); + useQuery(query, { variables: { req: "req", opt: "opt" } }); + }); + }); }); diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 33b127f62e6..97b8d1560fa 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -41,6 +41,37 @@ const { prototype: { hasOwnProperty }, } = Object; +interface QueryHookOptionsWithVariables< + TData, + TVariables extends OperationVariables, +> extends Omit, "variables"> { + variables: TVariables; +} + +type OnlyRequiredProperties = { + [K in keyof T as {} extends Pick ? never : K]-?: T[K]; +}; + +type HasRequiredVariables = + {} extends OnlyRequiredProperties ? FalseCase : TrueCase; + +export function useQuery< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + query: DocumentNode | TypedDocumentNode, + ...[options]: HasRequiredVariables< + TVariables, + [ + optionsWithVariables: QueryHookOptionsWithVariables< + NoInfer, + NoInfer + >, + ], + [options?: QueryHookOptions, NoInfer>] + > +): QueryResult; + export function useQuery< TData = any, TVariables extends OperationVariables = OperationVariables, From 80b52fc8af36fedc349407b909baf98a7bb312aa Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 22 Sep 2023 14:19:34 +0200 Subject: [PATCH 2/4] changeset --- .changeset/slow-planets-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slow-planets-design.md diff --git a/.changeset/slow-planets-design.md b/.changeset/slow-planets-design.md new file mode 100644 index 00000000000..335c5c79461 --- /dev/null +++ b/.changeset/slow-planets-design.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +useQuery: `variables/options` should only be required if `TVariables` is empty or default From dbbbd10b1a5f2c23c71fe1a7a6e38f0c60d662a6 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 22 Sep 2023 14:42:47 +0200 Subject: [PATCH 3/4] fixup generic `Query` HOC types --- src/react/components/Query.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/components/Query.tsx b/src/react/components/Query.tsx index 1e5611f646a..3953e01d14c 100644 --- a/src/react/components/Query.tsx +++ b/src/react/components/Query.tsx @@ -9,7 +9,7 @@ export function Query< TVariables extends OperationVariables = OperationVariables, >(props: QueryComponentOptions) { const { children, query, ...options } = props; - const result = useQuery(query, options); + const result = useQuery(query, options); return result ? children(result as any) : null; } From f3aecc723ce0dc0e19b1048118da3b9c9610cf59 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 22 Sep 2023 15:05:21 +0200 Subject: [PATCH 4/4] Update .changeset/slow-planets-design.md --- .changeset/slow-planets-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/slow-planets-design.md b/.changeset/slow-planets-design.md index 335c5c79461..72cc2f0e259 100644 --- a/.changeset/slow-planets-design.md +++ b/.changeset/slow-planets-design.md @@ -2,4 +2,4 @@ "@apollo/client": patch --- -useQuery: `variables/options` should only be required if `TVariables` is empty or default +useQuery: `variables/options` should be required if `TVariables` is not empty/purely optional/default