DEV Community

Andrew Lee
Andrew Lee

Posted on

Apollo Client: Hooks, Fragments, Queries, Mutations, and Generated Types

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
Enter fullscreen mode Exit fullscreen mode

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.

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>
);
Enter fullscreen mode Exit fullscreen mode

Query in React

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

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
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode
// In our React component
const { data, loading } = useQuery(PACK_QUERY)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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).

"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__"
Enter fullscreen mode Exit fullscreen mode

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

npm run gql-gen
Enter fullscreen mode Exit fullscreen mode

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

import { PackDiscoverPageQuery } from "./__generated__/PackDiscoverPageQuery";
Enter fullscreen mode Exit fullscreen mode

And then specify the type when calling the useQuery hook.

const { data, loading } = useQuery<PackDiscoverPageQuery>(PACKS_QUERY);
Enter fullscreen mode Exit fullscreen mode

Mutations

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

import { PackDetailsPageGameCreateMutation } from "./__generated__/PackDetailsPageGameCreateMutation";
Enter fullscreen mode Exit fullscreen mode
const GAME_CREATE = gql`
  mutation PackDetailsPageGameCreateMutation($input: GameCreateInput!) {
    gameCreate(input: $input) {
      code
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode
const [gameCreate] = useMutation<PackDetailsPageGameCreateMutation>(GAME_CREATE);

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

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:

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
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

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}
`;
Enter fullscreen mode Exit fullscreen mode

We can define fragments with the components like the following:

Navigation.fragments = {
  pack: gql`
    fragment NavigationPackFragment on Pack {
      id
      name
    }
  `
};
Enter fullscreen mode Exit fullscreen mode
Sidebar.fragments = {
  pack: gql`
    fragment SidebarPackFragment on Pack {
      id
      acts(first: 100) {
        edges {
          node {
            id
            question
            answer
            instruction
            questionType {
              id
              slug
            }
            answerType {
              id
              slug
            }
          }
        }
      }
    }
  `
};
Enter fullscreen mode Exit fullscreen mode
ActPreview.fragments = {
  act: gql`
    fragment ActPreviewFragment on Act {
      id
      question
      answer
      instruction
      questionType {
        id
        slug
      }
      answerType {
        id
        slug
      }
    }
  `
}
Enter fullscreen mode Exit fullscreen mode

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

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

type Props = {
  act: ActPreviewFragment;
};

const ActPreviewFragment = ({ act }: Props) => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
vitya_obolonsky profile image
viktor_k

piece of shit