Categories
React

React: How to use Apollo with ‘Union Types’ in GraphQL

Apollo is one of the most popular React library to work with GraphQL APIs. However they don’t have native support when your APIs contain union types.

Let’s look at an example API that contains union types and how we can work with them using Apollo. Assuming you have an API like this:

{
  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

that returns this response….

{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "__typename": "Human",
        "name": "Leia Organa",
        "height": 1.5
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}

If you use the above query via Apollo, you will see this warning messages in the browser console:

You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types. Apollo Client will not be able to accurately map fragments. To make this error go away, use the `IntrospectionFragmentMatcher` as described in the docs: https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher

This is because Apollo by default uses a simple heuristics matching to match the response string into the Response object. We need to do some extra work to tell Apollo how to parse the response with union types.

Apollo has a separate section on their docs about this.

Step 1: Save the information about the fragment types in a separate file:

const fetch = require('node-fetch');
const fs = require('fs');

fetch(`${YOUR_API_HOST}/graphql`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    variables: {},
    query: `
      {
        __schema {
          types {
            kind
            name
            possibleTypes {
              name
            }
          }
        }
      }
    `,
  }),
})
  .then(result => result.json())
  .then(result => {
    // here we're filtering out any type information unrelated to unions or interfaces
    const filteredData = result.data.__schema.types.filter(
      type => type.possibleTypes !== null,
    );
    result.data.__schema.types = filteredData;
    fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => {
      if (err) {
        console.error('Error writing fragmentTypes file', err);
      } else {
        console.log('Fragment types successfully extracted!');
      }
    });
  });

The JSON file should look something like this:

{
  "__schema": {
    "types": [
      {
        "kind": "UNION",
        "name": "UnionSearchDetails",
        "possibleTypes": [
          {
            "name": "Human"
          },
          {
            "name": "Droid"
          },
          {
            "name": "Starship"
          }
        ]
      }
    ]
  }
}

Step 2: Create fragmentMatcher using the JSON file from the previous step:

import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import introspectionQueryResultData from './fragmentTypes.json';

const fragmentMatcher = new IntrospectionFragmentMatcher({
  introspectionQueryResultData
});

Step 3: Now include this fragmentMatcher in cache object and pass it to the Apollo client:

import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';

// add fragmentMatcher code from step 2 here

const cache = new InMemoryCache({ fragmentMatcher });

const client = new ApolloClient({
  cache,
  link: new HttpLink(),
});

Now all done. The warnings and errors that you noticed previously should have gone away and the Response object from Apollo should contain the correct data.

How to use ‘Union types’ in tests and storybooks?

Now that you have fixed the problem for production code, how can we fix it for your tests or storybooks?

Apollo provides MockedProvider for this purpose. Here is a sample:

<MockedProvider mocks={mocks} addTypename cache={cache}>
     <MyComponenent />
</MockedProvider>

The important change here from the sample code is that addTypename has to be true, to allow Apollo to use the __typename in the response to map the object to the correct type.

Leave a Reply

Your email address will not be published. Required fields are marked *