From 71f6ee30de705a5c364cdceabb9ea1869973e45c Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 23 Jul 2026 18:16:43 -0600 Subject: [PATCH 1/3] Add failing type test for constant variable types with modern signatures --- .../type-tests/signatures/modern/useQuery.ts | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/integration-tests/type-tests/signatures/modern/useQuery.ts b/integration-tests/type-tests/signatures/modern/useQuery.ts index 4b829bfe57f..921ec2251bd 100644 --- a/integration-tests/type-tests/signatures/modern/useQuery.ts +++ b/integration-tests/type-tests/signatures/modern/useQuery.ts @@ -367,6 +367,168 @@ test("requires variables with mixed TVariables", () => { ); }); +// https://github.com/apollographql/apollo-client/issues/13342 +test("constant variable types do not widen returnPartialData", () => { + { + const query: TypedDocumentNode<{ character: string }, { type: "main" }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { type: "main" }, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { type: "main" }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { type: "main" }, + returnPartialData: false, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { type: "main" }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { type: "main" }, + returnPartialData: true, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | DataValue.Partial<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf< + "empty" | "partial" | "streaming" | "complete" + >(); + } + + { + const query: TypedDocumentNode<{ character: string }, { episode: 10 }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { episode: 10 }, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { episode: 10 }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { episode: 10 }, + returnPartialData: false, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { episode: 10 }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { episode: 10 }, + returnPartialData: true, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | DataValue.Partial<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf< + "empty" | "partial" | "streaming" | "complete" + >(); + } + + { + const query: TypedDocumentNode<{ character: string }, { main: true }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { main: true }, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { main: true }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { main: true }, + returnPartialData: false, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf<"empty" | "streaming" | "complete">(); + } + + { + const query: TypedDocumentNode<{ character: string }, { main: true }> = + gql``; + + const { data, dataState } = useQuery(query, { + variables: { main: true }, + returnPartialData: true, + }); + + expectTypeOf(data).toEqualTypeOf< + | DataValue.Complete<{ character: string }> + | DataValue.Streaming<{ character: string }> + | DataValue.Partial<{ character: string }> + | undefined + >(); + expectTypeOf(dataState).toEqualTypeOf< + "empty" | "partial" | "streaming" | "complete" + >(); + } +}); + test("always returns empty data/dataState with unconditional skipToken", () => { const query: TypedDocumentNode< { character: string }, From f4b8ab3440f08dcf4e6c5eb7dda0c513f7370e66 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 23 Jul 2026 21:24:43 -0600 Subject: [PATCH 2/3] Capture returnPartialData in a separate generic arg --- src/react/hooks/useQuery.ts | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 97ef002471b..e32bab5e7b2 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -205,6 +205,7 @@ export declare namespace useQuery { | Record // no options | Options | SkipToken, + TReturnPartialData extends boolean | undefined = undefined, > = LazyType< Result< TData, @@ -215,7 +216,7 @@ export declare namespace useQuery { | (TOptions extends any ? TOptions extends SkipToken ? never : OptionWithFallback< - TOptions, + { returnPartialData: TReturnPartialData }, DefaultOptions, "returnPartialData" > extends false ? @@ -547,16 +548,23 @@ export declare namespace useQuery { >]?: never; } >, + TReturnPartialData extends boolean | undefined = undefined, >( query: DocumentNode | TypedDocumentNode, ...[options]: // we generally do not allow for a `TVariables` of `never` // TODO: check if we need a similar check in other hooks [TVariables] extends [never] ? [options: never] : // variables optional - {} extends TVariables ? [options?: TOptions] + {} extends TVariables ? + [options?: TOptions & { returnPartialData?: TReturnPartialData }] : // variables required - [options: TOptions] - ): useQuery.ResultForOptions; + [options: TOptions & { returnPartialData?: TReturnPartialData }] + ): useQuery.ResultForOptions< + TData, + TVariables, + TOptions, + TReturnPartialData + >; /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1)} */ < @@ -583,16 +591,31 @@ export declare namespace useQuery { >]?: never; } >, + TReturnPartialData extends boolean | undefined = undefined, >( query: DocumentNode | TypedDocumentNode, ...[options]: // we generally do not allow for a `TVariables` of `never` // TODO: check if we need a similar check in other hooks [TVariables] extends [never] ? [options: never] : // variables optional - {} extends TVariables ? [options?: TOptions | SkipToken] + {} extends TVariables ? + [ + options?: + | (TOptions & { returnPartialData?: TReturnPartialData }) + | SkipToken, + ] : // variables required - [options: TOptions | SkipToken] - ): useQuery.ResultForOptions; + [ + options: + | (TOptions & { returnPartialData?: TReturnPartialData }) + | SkipToken, + ] + ): useQuery.ResultForOptions< + TData, + TVariables, + TOptions | SkipToken, + TReturnPartialData + >; ssrDisabledResult: ObservableQuery.Result; } From f1d25c9d70657ca31afc1926d6e25fa2959878e2 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Fri, 24 Jul 2026 10:37:08 -0600 Subject: [PATCH 3/3] Proposed solution for useQuery --- src/react/hooks/useQuery.ts | 85 ++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index e32bab5e7b2..6644a5f40e0 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -201,10 +201,6 @@ export declare namespace useQuery { export type ResultForOptions< TData, TVariables extends OperationVariables, - TOptions extends - | Record // no options - | Options - | SkipToken, TReturnPartialData extends boolean | undefined = undefined, > = LazyType< Result< @@ -213,16 +209,13 @@ export declare namespace useQuery { | "complete" | "streaming" | "empty" - | (TOptions extends any ? - TOptions extends SkipToken ? never - : OptionWithFallback< - { returnPartialData: TReturnPartialData }, - DefaultOptions, - "returnPartialData" - > extends false ? - never - : "partial" - : never) + | (OptionWithFallback< + { returnPartialData: TReturnPartialData }, + DefaultOptions, + "returnPartialData" + > extends false ? + never + : "partial") > >; @@ -532,22 +525,16 @@ export declare namespace useQuery { DocumentNode | TypedDocumentNode : // this overload should only be accessible if all `TVariables` are optional never - ): useQuery.ResultForOptions>; + ): useQuery.ResultForOptions; /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1)} */ < TData, TVariables extends OperationVariables, // this overload should never be manually defined, it should always be inferred - TOptions extends useQuery.Options> & - VariablesOption< - TVariables & { - [K in Exclude< - keyof TOptions["variables"], - keyof TVariables - >]?: never; - } - >, + TProvidedVariables extends TVariables & { + [K in Exclude]?: never; + } = TVariables, TReturnPartialData extends boolean | undefined = undefined, >( query: DocumentNode | TypedDocumentNode, @@ -556,15 +543,20 @@ export declare namespace useQuery { [TVariables] extends [never] ? [options: never] : // variables optional {} extends TVariables ? - [options?: TOptions & { returnPartialData?: TReturnPartialData }] + [ + options?: useQuery.Base.Options> & { + variables?: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }, + ] : // variables required - [options: TOptions & { returnPartialData?: TReturnPartialData }] - ): useQuery.ResultForOptions< - TData, - TVariables, - TOptions, - TReturnPartialData - >; + [ + options: useQuery.Base.Options> & { + variables: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }, + ] + ): useQuery.ResultForOptions; /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1)} */ < @@ -582,15 +574,9 @@ export declare namespace useQuery { TData, TVariables extends OperationVariables, // this overload should never be manually defined, it should always be inferred - TOptions extends useQuery.Options> & - VariablesOption< - TVariables & { - [K in Exclude< - keyof TOptions["variables"], - keyof TVariables - >]?: never; - } - >, + TProvidedVariables extends TVariables & { + [K in Exclude]?: never; + } = TVariables, TReturnPartialData extends boolean | undefined = undefined, >( query: DocumentNode | TypedDocumentNode, @@ -601,21 +587,22 @@ export declare namespace useQuery { {} extends TVariables ? [ options?: - | (TOptions & { returnPartialData?: TReturnPartialData }) + | (useQuery.Base.Options> & { + variables?: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }) | SkipToken, ] : // variables required [ options: - | (TOptions & { returnPartialData?: TReturnPartialData }) + | (useQuery.Base.Options> & { + variables: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }) | SkipToken, ] - ): useQuery.ResultForOptions< - TData, - TVariables, - TOptions | SkipToken, - TReturnPartialData - >; + ): useQuery.ResultForOptions; ssrDisabledResult: ObservableQuery.Result; }