Search code examples
reactjsgraphqlgraphql-codegen

Using keys with graphql fragment masking


As far as I know, fragment masking is considered a best practice when developing graphql clients, but I'm having some difficulties wrapping my head around how to write even some simple react necessities with that level of obscurity. One common necessity is providing key properties in iterations:

One example I'm working on is pulling repository data from Github to print cards with each of them. The query is:

fragment RepositoryCard on Repository {
  resourcePath
  description
}

Then, I'd use this query in a bigger one that request a user profile and gets some of their repositories:

    query GetUserData($login: String!) {
        user(login: $login) {
            bio
            name
            repositories(first: 10) {
                edges {
                    node {
                        ...RepositoryCard
                    }
                }
            }
        }
    }

So far, so good. Then I'd map the responses to cards:

{
  data?.user?.repositories?.edges?.map((repository) => (
    <RepositoryCard 
      className="my-2"
      repositoryNode={repository?.node}
    />
  ))
}

But then, I need a key prop for this iteration. The best approach would be to use the resourcePath since that's unique. However, since fragment masking is used, graphql-codegen doesn't allow me to see the contents of the type of repository.node, so I can't access resourcePath from outside of the component to get it.

What's the common approach to solve this?


Solution

  • It seems like useFragment is not a real hook, so it isn't necessary to follow the rules of hooks. In an answer to another question I see that the solution is to simply rename that function to something that won't trigger warnings (and better show the fact that it isn't a hook), so in your codegen.ts:

      generates: {
        "./src/__generated__/": {
          preset: "client",
          plugins: [],
          presetConfig: {
            gqlTagName: "gql",
            fragmentMasking: {
              unmaskFunctionName: "getFragmentData",
            }
          }
        }
    

    Then you can just call getFragmentData (previously useFragment) inside the map without getting any warnings:

    data?.repositories?.edges?.filter((r) => !!(r?.node))
                              .map((r) => r?.node)
                              .map((repository) => {
                                 const key = getFragmentData(REPOSITORY_CARD_FRAGMENT, repository)!.resourcePath;
                                 return (
                                    <RepositoryCard
                                       key={key}
                                       className="my-2"
                                       query={repository!}
                                     />
                                  );
                               })