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 }, diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 97ef002471b..6644a5f40e0 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -201,10 +201,7 @@ 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< TData, @@ -212,16 +209,13 @@ export declare namespace useQuery { | "complete" | "streaming" | "empty" - | (TOptions extends any ? - TOptions extends SkipToken ? never - : OptionWithFallback< - TOptions, - DefaultOptions, - "returnPartialData" - > extends false ? - never - : "partial" - : never) + | (OptionWithFallback< + { returnPartialData: TReturnPartialData }, + DefaultOptions, + "returnPartialData" + > extends false ? + never + : "partial") > >; @@ -531,32 +525,38 @@ 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, ...[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?: useQuery.Base.Options> & { + variables?: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }, + ] : // variables required - [options: TOptions] - ): useQuery.ResultForOptions; + [ + options: useQuery.Base.Options> & { + variables: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }, + ] + ): useQuery.ResultForOptions; /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1)} */ < @@ -574,25 +574,35 @@ 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, ...[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?: + | (useQuery.Base.Options> & { + variables?: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }) + | SkipToken, + ] : // variables required - [options: TOptions | SkipToken] - ): useQuery.ResultForOptions; + [ + options: + | (useQuery.Base.Options> & { + variables: TProvidedVariables; + returnPartialData?: TReturnPartialData; + }) + | SkipToken, + ] + ): useQuery.ResultForOptions; ssrDisabledResult: ObservableQuery.Result; }