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

Incomprehensible type mismatch with query generics #11828

Closed
StrikeAgainst opened this issue May 3, 2024 · 4 comments
Closed

Incomprehensible type mismatch with query generics #11828

StrikeAgainst opened this issue May 3, 2024 · 4 comments

Comments

@StrikeAgainst
Copy link

StrikeAgainst commented May 3, 2024

Issue Description

In an effort to abstract the use of queries for various similar situations, I wrapped a query inside a function typed with an extended generic for the result TData. When applying this composable onto a variable typed with a more general result type, TS is giving me an error that does not make much sense to me. Below I have a condensed use case for the issue:

const getById = <TData extends Record<string, string>>(gql: DocumentNode, id: string) => {
  return useQuery<TData>(
    gql,
    computed(() => ({ id }))
  )
}

type ByIdQuery<TData extends Record<string, string>> = ReturnType<typeof getById<TData>>

const query: ByIdQuery<Record<string, string>> = getById<{ foo: string }>(MyQuery, "1")       <--- error
const queryFoo: ByIdQuery<{ foo: string }> = getById<{ foo: string }>(MyQuery, "1")

Basically TData extends Record<string, string>, where { foo: string } in theory should be assignable onto. While I can assign the return value of getById<{ foo: string }> to a variable of ByIdQuery<{ foo: string }>, I cannot apply it onto the more general type of ByIdQuery<Record<string, string>>. In the error stack, somewhere along the line, Record<string, string> and { foo: string; } are suddenly swapped then, with no indication as to why this happens.

Type 'NextFetchPolicyContext<{ foo: string; }, OperationVariables>' is not assignable to type 'NextFetchPolicyContext<Record<string, string>, OperationVariables>'.
Type 'Record<string, string>' is not assignable to type '{ foo: string; }'

The minimal reproducible case would be this

import { WatchQueryOptions } from "@apollo/client/core/watchQueryOptions";

const optionsA: WatchQueryOptions<{}, { foo: string }> = {} as WatchQueryOptions<{}, { foo: string }>
const optionsB: WatchQueryOptions<{}, Record<string, string>> = optionsA

which is also implemented in the reproduction linked below, where the error is also indicated.

Link to Reproduction

https://codesandbox.io/p/sandbox/youthful-thompson-d48qtm

@apollo/client version

3.10.1

@StrikeAgainst StrikeAgainst changed the title Incomprehensible type mismatch with ObservableQuery generics Incomprehensible type mismatch with query generics May 3, 2024
@phryneas
Copy link
Member

phryneas commented May 6, 2024

This is because the return value of useQuery uses TData both in covariant and contravariant positions.

I'd recommend that you use TypedDocumentNode here and then let inference take over:

const getById = <TData>(gql: TypedDocumentNode<TData>, id: string) => {
  return useQuery(
    gql,
    computed(() => ({ id }))
  )
}

and then use it like

const result = getById(MyQuery, "1")

Assuming that your queries are typed correctly (and this is probably the single most important thing to do when using GraphQL with TypeScript!), everything should be typed correctly from there on.
Your manual annotations only add inconsistencies and possibly type erasure, so I'd try to avoid those.

That said, this still breaks the rule of hooks.

What is the real value here instead of just using

const result = useQuery(gql, computed(() => ({ id })))

in your component?

@StrikeAgainst
Copy link
Author

Thanks for the hint, I actually forgot about the concept of contravariance, also I was not aware that there is TypedDocumentNode.

I'm currently abstracting CRUD operations for a multitude of different entity types, where all come with their own queries and result / variables types, hence why I need some form of generic typing for - in this case - an ID-based single entity query. With your hint I got the typing down correct now though, so thanks a lot!

Copy link
Contributor

github-actions bot commented May 7, 2024

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

Copy link
Contributor

github-actions bot commented Jun 7, 2024

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants