Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Type safe usage of @graphql in TypeScript projects #635

Closed
jtmthf opened this issue Apr 17, 2017 · 4 comments
Closed

Type safe usage of @graphql in TypeScript projects #635

jtmthf opened this issue Apr 17, 2017 · 4 comments

Comments

@jtmthf
Copy link

jtmthf commented Apr 17, 2017

Steps to Reproduce

I'm working on a TypeScript project and would like to use the graphql HOC as a decorator if possible. Currently my code looks like this below which I saw is similar to how @graphql is used in the tests of this library. It works, but it really defeats the purpose of using TypeScript as all type safety is lost.

const query = gql`
  query {
    allMessages(first:1) {
      text
    }
  }
`;

@graphql(query)
class GraphQLMessage extends React.PureComponent<any, any> {
  
  render() {
    const { data } = this.props;
    if (!data) {
      return null;
    }
    const message = data.allMessages && data.allMessages[0].text;
    const isLoading = data.loading ? 'yes' : 'nope';
    return (
      <div>
        <h2>Message from GraphQL server: <em>{message}</em></h2>
        <h2>Currently loading?: {isLoading}</h2>
      </div>
    );
  }
}

Taking inspiration from react-redux I messed around with the declaration of graphql.d.ts until I got this.

export interface WrapWithApollo< TDataProps, TVarProps > {
    (component: ComponentClass<InjectedGraphQLProps<TDataProps>> | StatelessComponent<InjectedGraphQLProps<TDataProps>>): ComponentClass<TVarProps>;
}
export default function graphql<TDataProps, TVarProps>(
    document: DocumentNode, operationOptions?: OperationOption
): WrapWithApollo<TDataProps, TVarProps >;

This then let me write my component as this which is a notable improvement and is fully type safe. The only real downside is a bit of boilerplate and that if the component has no state, void and {} don't work as generic arguments.

interface MessageData {
  allMessages?: Array<{
    text: string;
  }>;
}

@graphql<MessageData, {}>(query)
class GraphQLMessage extends React.PureComponent<InjectedGraphQLProps<MessageData>, any> {
  
  render() {
    const { data } = this.props;
    if (!data) {
      return null;
    }
    const message = data.allMessages && data.allMessages[0].text;
    const isLoading = data.loading ? 'yes' : 'nope';
    return (
      <div>
        <h2>Message from GraphQL server: <em>{message}</em></h2>
        <h2>Currently loading?: {isLoading}</h2>
      </div>
    );
  }
}

Expected Behavior

It would be great to see use of the graphql decorator as more type safe. I know my current solution is a bit weird, and I'm wondering if anyone else has hacked around with this and came up with another solution?

@stubailo
Copy link
Contributor

I think that's a great idea. We already do this kind of generic stuff in the watchQuery method on ApolloClient, which is the way graphql works under the hood: https://github.com/apollographql/apollo-client/blob/1d2329fc008716d6ec9c3712b813d03b894b1a9d/src/ApolloClient.ts#L287

I imagine adding something similar to the container would be great!

We're also looking for people to help improve the developer experience and correctness of the TypeScript target for https://github.com/apollographql/apollo-codegen, which will enable you to automatically generate the TypeScript definitions for the query! I know @lewisf is going to work on the Flow stuff a bit soon, so you could coordinate :]

@jtmthf
Copy link
Author

jtmthf commented Apr 17, 2017

I actually happen to have a WIP PR open on apollo-codegen for a TypeScript rewrite of the whole code base, that's been delayed by life aka school.

I've also seen that the people behind the react-redux typings have struggled with using connect as a decorator as well. I swapped out classes for recompose and it works pretty well. This is what it looks like now

const connect = compose<InjectedGraphQLProps<MessageData>, {}>(
  graphql(query),
  pure
);

const GraphQLMessage = connect(({data}) => {
  const message = data!.allMessages && data!.allMessages![0].text;
  const isLoading = data!.loading ? 'yes' : 'nope';
  return (
    <div>
      <h2>Message from GraphQL server: <em>{message}</em></h2>
      <h2>Currently loading?: {isLoading}</h2>
    </div>
  );
});

Once again all type safe. The downside is the recompose typings don't allow retuning null so that's why strictNullChecks is bypassed on data access. The only thing missing is supplying variables to the query as props. I realized that could be fixed by rewriting the functional component decoration to read as

const GraphQLMessage: React.ComponentClass<MessageVars>

What I could see being really helpful is documentation on use with TypeScript and examples of common TypeScript patterns. Ideally those principles should apply to flow as well.

@jbaxleyiii
Copy link
Contributor

#394 will be a solution to this! I'll be picking it up this week!

@jbaxleyiii
Copy link
Contributor

fixed via #695!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants