AWS Amplify GraphQL Queries with TypeScript and Hooks

Mat Warger AWS, JavaScript, React, Technology Snapshot, Tutorial, TypeScript Leave a Comment

Originally posted by Mat Warger on mw.codes April 19, 2019.

I’m a big fan of AWS Amplify. I’m also a big fan of TypeScript.

Amplify is not built with TypeScript, and to use it effectively, sometimes you need to give it a little help, especially when it comes to GraphQL. With the advent of hooks, we can create some nice utilities for ourselves that let us leverage the power of TypeScript with our GraphQL queries. Let’s see what that looks like.

I’ll be assuming familiarity with React and TypeScript, including the usage of Amplify and GraphQL for this post. There are tons of great resources online. Here’s a great set of steps to set up a similar project. Or, you can clone the sample app and run amplify init at the root of the project to see the final result.

Simple Query

From our sample app we have a list of popular games we want to retrieve. The query looks like this:

popularGames {
    id
    name
    popularity
    url
    summary
    # omitted for brevity
}

Starting out, this is a great first start to what we’re going for. We want to use useEffect to handle fetching our data. We’re using Amplify’s graphql method and passing the GraphQL query from above to the graphqlOperation method.

React.useEffect(() => {
  const fetchGames = async () => {
    try {
      const response = await API.graphql(graphqlOperation(popularGames));
    } catch (error) {
      console.log(error);
    }
  };

  fetchGames();
}, []);
The response objects has a data property, which contains our list of games. Here's a couple from the list.

{
  "data": {
    "popularGames": [
      {
        "id": "76882",
        "name": "Sekiro: Shadows Die Twice",
        "popularity": 3954.25
      },
      {
        "id": "114455",
        "name": "Pacify",
        "popularity": 1472.0
      }
    ]
  }
}

We want to display these on our page, so we need to load them into state. With hooks, you accomplish this by creating a useState declaration and then using the method created for you to load them into state.

const [gameData, setGameData] = React.useState(undefined);

React.useEffect(() => {
  const fetchGames = async () => {
    try {
      const response: any = await API.graphql(graphqlOperation(popularGames));
      setGameData(response.data);
    } catch (error) {
      console.log(error);
    }
  };

  fetchGames();
}, []);

At this point, you could display your games on the page using the gameData object.

See Also:  C# On The Client Side With Blazor

But in TypeScript land, we actually have more problems here. By initializing the state object to undefined, TypeScript can only infer that the value allowed for the gameData object is undefined, and will give us an error if we try to set our query response using the setGameData method. Additionally, there is a keyword we have used to get past this problem that bites many a first-time TypeScript developer in the ass.

any

This word will strike fear into the hearts of all who witness it. At least, it should if you want your future self to thank your past self at some point (Thanks, Paul, for this great insight).

We don’t want anys anywhere.

We really want to try our best to give ourselves and our tools as much power as possible.

We can’t tell what our data will be right now, which is a problem… but Amplify can help us.

Types to the Rescue

In our project, if it’s not configured already, we need to run amplify configure codegen. This will setup code generation and walk us through the process of generating types based on our queries. This is super helpful (and as far as I’m concerned, should be the default if a .tsconfig file is present at the root of the project…).

This gives us a type that we can use for our data. Normally, we could just throw this after the variable and be done with it.

const response: { data: PopularGamesQuery } = await API.graphql(
  graphqlOperation(popularGames)
);

We know that response is going to be this type if the operation doesn’t fail. However, the types returned by the graphql function are a mashup of GraphQLResult and Observable since this same function is used for both. Until this changes, we’re going to let it know what we expect to get back. We can do this by using the as keyword to tell TypeScript what we know our type will be.

const response = (await API.graphql(graphqlOperation(popularGames))) as {
  data: PopularGamesQuery;
};

Now we get the type help that we want. Hovering over the response object confirms that TypeScript recognizes the response to be an object with a data property of type PopularGamesQuery.

Games, setState

We’ll use the same type to tell our useState function how we want to utilize our gameData object.

See Also:  React Native With Expo

useState accepts a generic type parameter that allows us to inform the function about the type we want to use.

const [gameData, setGameData] = React.useState<PopularGamesQuery | undefined>(
  undefined
);

Here, we have passed the same PopularGamesQuery type (as well as the possible undefined value we use to initialize it with). This consoles the TypeScript compiler and we can move forward with displaying our games.

As simple as this is, there are a few boilerplate-y things that are staring us in the face. We can extract these things out and create some re-usable code that will make future queries in other components much easier to set up. Custom hooks to the rescue!

Stay tuned for the next post where we do just that!

What Do You Think?