Open lemonmade opened 5 years ago
My solution to this is to use graphql-code-generator
. I have some custom templates, but that's a detail. As the output, I get a code like this...
export const QUserMenuLanguagesDocument = gql`
query QUserMenuLanguages {
languages {
code
enum
rawCode
}
}
`
export function useQUserMenuLanguages(
variables?: QUserMenuLanguagesVariables,
baseOptions?: Hooks.QueryHookOptions<QUserMenuLanguagesVariables>,
) {
return Hooks.useQuery<QUserMenuLanguagesQuery, QUserMenuLanguagesVariables>(
QUserMenuLanguagesDocument,
variables,
baseOptions,
)
}
That way I don't need to care about types at all because the useQUserMenuLanguages
is already fully typed and everything that comes from it as well.
Hey @FredyC, thanks for commenting! That solution definitely works, but it's not ideal from my perspective. If I was dealing with only a few GraphQL documents, it would be totally doable, but I'm dealing with apps that have dozens or hundreds of documents. Forcing every developer to always remember to get the right variables and data types, and create a separate hook for their query, is not ideal. Additionally, we use the documents in more than just app code; in test code, we use it to provide types that are used to validate a "partial data" argument:
fillGraphQL(myQuery, {foo: 'bar'});
// ^ should be a valid partial version of myQuery’s data
fillGraphQL(myQuery, ({id}) => ({id}));
//. ^ should align with the type of myQuery’s variables
We could construct an extra "filler" for each query, too, but you can see how it really balloons. We also sometimes use the document types in a location where you can't provide a typed alternative. One such spot is custom assertions in test, where we validate that queries are called with particular arguments, and want to make sure the variables passed are valid given the known types of the query itself:
expect(graphQL).toHavePerformedGraphQLOperation(myQuery, {id: '123'});
// ^ should be a partial of the allowed variables
I've also been doing this by overriding the type modules (but obviously, that's not pleasant/good practice). I've found that it essentially works 'for free' - but I might be missing something, I'd be curious to know if I am.
In the same way as @lemonmade did, DocumentNode
takes the data and variables types as generics. All the functions that consume a DocumentNode
can link generics to its data and variables, automatically inferring the needed arguments (but this is entirely optional as rather than DocumentNode
consumer setting the defaults, the definition of DocumentNode
does it. In this example I've simply overridden the definitions so anyone can try it, but ideally it'd be native to the definitions. Obviously it'd need some tweaks to make variables required when not undefined.
declare module "graphql" {
import { OperationVariables } from '@apollo/react-common';
import { DocumentNode as OriginalDocumentNode } from "graphql/index";
export * from "graphql/index";
export interface DocumentNode<TData = any, TVariables = OperationVariables> extends OriginalDocumentNode {}
}
declare module "@apollo/react-hooks" {
export * from "@apollo/react-hooks/lib/index";
import { QueryResult } from '@apollo/react-common';
import { QueryHookOptions } from '@apollo/react-hooks/lib/types';
import { DocumentNode } from 'graphql';
export function useQuery<TData, TVariables>(query: DocumentNode<TData, TVariables>, options?: QueryHookOptions<TData, TVariables>): QueryResult<TData, TVariables>;
// etc...
}
Then when you use it TypeScript infers the types for you. It works perfectly fine with manually specifying them too from my testing.
// your query and its types from whatever generator
export const myQuery: DocumentNode<MyQuery, MyQueryVariables> = gql`
...
`
export const MyComponent: FunctionComponent = () => {
// both 'data' and 'variables' have MyQuery, MyQueryVariables automatically
const { data } = useQuery(myQuery, {
variables: {
...
}
})
if (data) {
return <p>Count: { data.count }</p>
}
return null
}
My solution to this is to use
graphql-code-generator
. I have some custom templates, but that's a detail. As the output, I get a code like this...export const QUserMenuLanguagesDocument = gql` query QUserMenuLanguages { languages { code enum rawCode } } ` export function useQUserMenuLanguages( variables?: QUserMenuLanguagesVariables, baseOptions?: Hooks.QueryHookOptions<QUserMenuLanguagesVariables>, ) { return Hooks.useQuery<QUserMenuLanguagesQuery, QUserMenuLanguagesVariables>( QUserMenuLanguagesDocument, variables, baseOptions, ) }
That way I don't need to care about types at all because the
useQUserMenuLanguages
is already fully typed and everything that comes from it as well.
This is autogenerated? Please share your setup
Migrated from: apollographql/react-apollo#3054