DEV Community

loading...

AWS Amplify GraphQL Operations with TypeScript and Hooks - Part 1 [Queries]

mwarger profile image Mat Warger Updated on ・4 min read

I'm a big fan of 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. This is especially true 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 setup 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
    # ommitted 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.

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.

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 boilerplatey 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 setup. Custom hooks to the rescue!

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

This post was originally posted on my website. You can see this and other articles like it, as well as what I'm all about. Follow me on @twitter and reach out if you have any questions!

Discussion (3)

pic
Editor guide
Collapse
ericclemmons profile image
Eric Clemmons

Your posts have been extremely helpful in crafting an RFC for React Hooks:

Please share & provide input as you see fit!

Collapse
sshetty2 profile image
Sachit Shetty @_@ Sonny

Mat, the code seems to be incomplete in Github

...from queryHelpers.tsx

** Missing Dependencies **
import Spinner from "../components/ui/Spinner";
import { ErrorBox } from "../containers/auth/ErrorBox";
import useDeepCompareEffect from "use-deep-compare-effect";
import { UndefinedGQLType } from "../types/utils";
import { notEmpty } from "./common";
import { Observable } from "apollo-link";

Please advise

Collapse
mwarger profile image
Mat Warger Author

This is outdated. I will be posting about this soon.