Apollo Client: Hooks, Fragments, Queries, Mutations, and Generated Types
Tags: react, typescript, tutorial, graphql
Published: 2020-05-03 01:32:47

Apollo is a GraphQL client to query and mutate GraphQL APIs. In this tutorial, we are going to go over how to use it with React (hooks) and TypeScript. By utilizing generated types with fragments, we are going to take our development experience to the next level.

## Installation

```
npm i @apollo/react-hooks apollo-boost graphql
```

## Add to React

We can add Apollo to React by wrapping our root `App` component with the `ApolloProvider` and provide it an instance of the `ApolloClient`.

```js
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient from 'apollo-boost';

export const client = new ApolloClient({
  uri: `${process.env.REACT_APP_API_URL}/graphql`
});

const App = () => (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);
```

## Query in React

Now our React application is ready to start using hooks. We can query our GraphQL API with the `useQuery` hook.

```js
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';

const PACK_QUERY = gql`
  query PackDetailsPagePackQuery($packId: ID!) {
    currentUser {
      id
    }
    pack(id: $packId) {
      id
      name
      description
      imageUrl
      user {
        id
      }
    }
  }
`;
```

```js
// In our React component
const { data, loading } = useQuery(PACK_QUERY)
```

## Types

Now this is great, but it would be awesome if we could have the response typed. We are using TypeScript after all. Good news is that TypeScript and GraphQL is a match made in heaven. What's even better is that Apollo provides a set of tools that makes integrating the two technologies a breeze. Let's first download Apollo tools:

```
npm i apollo --save-dev
```

Then we can create two commands in our `package.json`. First command is `introspect-schema` which makes a request to our GraphQL API and generate a `schema.json` file for our Apollo client to use. The second command is `gql-gen` commmand which first calls the `instrospect-schema` command and then looks at all TypeScript files to generate types for queries, mutations, and fragments (will expand more on fragments later).

```json
"introspect-schema": "apollo schema:download --endpoint=http://localhost:4000/graphql schema.json" ,
"gql-gen": "npm run introspect-schema && apollo codegen:generate --localSchemaFile=schema.json --target=typescript --includes=src/**/*.tsx --tagName=gql --addTypename --globalTypesFile=src/__generated__/graphql-types.ts __generated__"
```

Now we can run the following command to have types generated for us:

```
npm run gql-gen
```

Once the types are generated, we can import it in our React component:

```js
import { PackDiscoverPageQuery } from "./__generated__/PackDiscoverPageQuery";
```

And then specify the type when calling the `useQuery` hook.

```js
const { data, loading } = useQuery<PackDiscoverPageQuery>(PACKS_QUERY);
```

## Mutations

Mutations work similarly to queries. There is a `useMutation` hook we can use and types will be generated for us.

```js
import { PackDetailsPageGameCreateMutation } from "./__generated__/PackDetailsPageGameCreateMutation";
```

```js
const GAME_CREATE = gql`
  mutation PackDetailsPageGameCreateMutation($input: GameCreateInput!) {
    gameCreate(input: $input) {
      code
    }
  }
`;
```

```js
const [gameCreate] = useMutation<PackDetailsPageGameCreateMutation>(GAME_CREATE);

const handleCreate = () => {
  const { data } = await gameCreate({
    variables: { input: { packId: packId || "" } }
  });
  // ...
}
```

## Fragments

Fragments will help us build reusable components. By specifying data requirements along with the component, we can combine all of the data requirements of a single page (combination of fragments from all components) and get the data in a single request.

For example, let's take a look at this following query:

```js
const PACK_QUERY = gql`
  query PackCreatorPagePackQuery($packId: ID!, $actId: ID) {
    pack(id: $packId) {
      id
      name
      acts(first: 100) {
        edges {
          node {
            id
            question
            answer
            instruction
            questionType {
              id
              slug
            }
            answerType {
              id
              slug
            }
          }
        }
      }
    }
    act(id: $actId, packId: $packId) {
      id
      question
      answer
      instruction
      questionType {
        id
        slug
      }
      answerType {
        id
        slug
      }
    }
  }
`;
```

This query is very long and it is not clear which components need what data. When we use fragments, it becomes clear which components need what data.

```js
const PACK_QUERY = gql`
  query PackCreatorPagePackQuery($packId: ID!, $actId: ID) {
    pack(id: $packId) {
      ...NavigationPackFragment
      ...SidebarPackFragment
    }
    act(id: $actId, packId: $packId) {
      ...ActPreviewFragment
    }
  }
  ${Navigation.fragments.pack}
  ${Sidebar.fragments.pack}
  ${ActPreview.fragments.act}
`;
```

We can define fragments with the components like the following:

```js
Navigation.fragments = {
  pack: gql`
    fragment NavigationPackFragment on Pack {
      id
      name
    }
  `
};
```

```js
Sidebar.fragments = {
  pack: gql`
    fragment SidebarPackFragment on Pack {
      id
      acts(first: 100) {
        edges {
          node {
            id
            question
            answer
            instruction
            questionType {
              id
              slug
            }
            answerType {
              id
              slug
            }
          }
        }
      }
    }
  `
};
```

```js
ActPreview.fragments = {
  act: gql`
    fragment ActPreviewFragment on Act {
      id
      question
      answer
      instruction
      questionType {
        id
        slug
      }
      answerType {
        id
        slug
      }
    }
  `
}
```

Our `gql-gen` script create types for our fragments as well which we can use to declare prop types in our components. 

```js
import { ActPreviewFragment } from "./__generated__/ActPreviewFragment";

type Props = {
  act: ActPreviewFragment;
};

const ActPreviewFragment = ({ act }: Props) => {
  // ...
}
```