w3resource

Using Apollo with TypeScript


As your application grows, you may find it helpful to include a type system to assist in development. Apollo supports type definitions for TypeScript out of the box. Both apollo-client and React Apollo ship with definitions in their npm associated packages, so installation should be done for you after the libraries are included in your project.

The most common need when using type systems with GraphQL is to type the results of an operation. Given that a GraphQL server's schema is strongly typed, we can even generate TypeScript definitions automatically using a tool like apollo-codegen.

Typing hooks

React Apollo's useQuery, useMutation and useSubscription hooks are fully typed, and Generics can be used to type both incoming operation variables and GraphQL result data, as we will be illustrating:

useQuery

import React from 'react';
import {useQuery} from '@apollo/react-hooks';
import gql from 'graphql-tag';
interface RocketInventory {
  id: number;
  model: string;
  year: number;
  stock: number;
}

interface RocketInventoryData {
  rocketInventory: RocketInventory[];
}

interface RocketInventoryVars {
  year: number;
}

const GET_ROCKET_INVENTORY = gql`
  query getRocketInventory($year: Int!) {
    rocketInventory(year: $year) {
      id
      model
      year
      stock
    }
  }
`;

export function RocketInventoryList() {
  const { loading, data } = useQuery<RocketInventoryData, RocketInventoryVars>(
    GET_ROCKET_INVENTORY,
    { variables: { year: 2019 } }
  );
  return (
    <div>
      <h3>Available Inventory</h3>
      {loading ? (
        <p>Loading ...</p>
      ) : (
        <table>
          <thead>
            <tr>
              <th>Model</th>
              <th>Stock</th>
            </tr>
          </thead>
          <tbody>
            {data && data.rocketInventory.map(inventory => (
              <tr>
                <td>{inventory.model}</td>
                <td>{inventory.stock}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

useMutation

import React, {useState} from 'react';
import {useMutation} from '@apollo/react-hooks';
import gql from 'graphql-tag';

const SAVE_ROCKET = gql`
  mutation saveRocket($rocket: RocketInput!) {
    saveRocket(rocket: $rocket) {
      model
    }
  }
`;

interface RocketInventory {
  id: number;
  model: string;
  year: number;
  stock: number;
}



interface NewRocketDetails {
  model: string;
  year: number;
  stock: number;
}

export function NewRocketForm() {
  const [model, setModel] = useState('');
  const [year, setYear] = useState(0);
  const [stock, setStock] = useState(0);

  const [saveRocket, { error, data }] = useMutation<
    { saveRocket: RocketInventory },
    { rocket: NewRocketDetails }
  >(SAVE_ROCKET, {
    variables: { rocket: { model, year: +year, stock: +stock } }
  });

  return (
    <div>
      <h3>Add a Rocket</h3>
      {error ? <p>Oh no! {error.message}</p> : null}
      {data && data.saveRocket ? <p>Saved!</p> : null}
      <form>
        <p>
          <label>Model</label>
          <input
            name="model"
            onChange={e => setModel(e.target.value)}
          />
        </p>
        <p>
          <label>Year</label>
          <input
            type="number"
            name="model"
            onChange={e => setYear(+e.target.value)}
          />
        </p>
        <p>
          <label>Stock</label>
          <input
            type="number"
            name="stock"
            onChange={e => setStock(e.target.value)}
          />
        </p>
        <button onClick={() => model && year && stock && saveRocket()}>
          Add
        </button>
      </form>
    </div>
  );
}

useSubscription

import React from 'react';
import { useSubscription } from '@apollo/react-hooks';
import gql from 'graphql-tag';

interface Message {
  content: string;
}


interface News {
  latestNews: Message;
}

const LATEST_NEWS = gql`
  subscription getLatestNews {
    latestNews {
      content
    }
  }
`;

export function LatestNews() {
  const { loading, data } = useSubscription<News>(LATEST_NEWS);
  return (
    <div>
      <h5>Latest News</h5>
      <p>
        {loading ? 'Loading...' : data!.latestNews.content}
      </p>
    </div>
  );
}

Typing Render Prop Components

Using Apollo together with TypeScript couldn't be easier than using it with component API released in React Apollo 2.1:

const ALL_PEOPLE_QUERY = gql`
  query All_People_Query {
    allPeople {
      people {
        id
        name
      }
    }
  }
`;

interface Data {
  allPeople: {
    people: Array<{ id: string; name: string }>;
  };
};

interface Variables {
  first: number;
};

const AllPeopleComponent = <Query<Data, Variables> query={ALL_PEOPLE_QUERY}>
  {({ loading, error, data }) => { ... }}
</Query>
);

Now the <Query /> component render prop function arguments are typed. Since we are not mapping any props coming into our component, nor are we rewriting the props passed down, we only need to provide the shape of our data and the variables for full typing to work! Everything else is handled by React Apollo's robust type definitions.

This approach is the exact same for the <Query/>, <Mutation/>,and <Subcription /> components! Learn it once, and get the best types ever with Apollo.

Extending components

In previous versions of React Apollo, render prop components (Query, Mutation and Subscription) could be extended to add additional type information:

class SomeQuery extends Query<SomeData, SomeVariables> {}

Since all class based render prop components have been converted to functional components, extending components in this manner is no longer possible. While we recommend switching over to use the new useQuery, useMutation and useSubscription hooks as soon as possible, if you're looking for a stop gap you can consider replacing your class with a wrapped and typed component:

export const SomeQuery = () => (
  <Query<SomeData, SomeVariables> query={SOME_QUERY} /* ... */>
    {({ loading, error, data }) => { ... }}
  </Query>
);

Typing Higher Order Components

Since the result of a query will be sent to the wrapped component as props, we want to be able to tell our type system the shape of those props. Here is an example setting types for an operation using the graphql higher order component :

import React from "react";
import gql from "graphql-tag";
import { ChildDataProps, graphql } from "react-apollo";

const HERO_QUERY = gql`
  query GetCharacter($episode: Episode!) {
    hero(episode: $episode) {
      name
      id
      friends {
        name
        id
        appearsIn
      }
    }
  }
`;

type Hero = {
  name: string;
  id: string;
  appearsIn: string[];
  friends: Hero[];
};

type Response = {
  hero: Hero;
};

type Variables = {
  episode: string;
};

type ChildProps = ChildDataProps<{}, Response, Variables>;

// Note that the first parameter here is an empty Object, which means we're
// not checking incoming props for type safety in this example. The next
// example (in the "Options" section) shows how the type safety of incoming
// props can be ensured.
const withCharacter = graphql<{}, Response, Variables, ChildProps>(HERO_QUERY, {
  options: () => ({
    variables: { episode: "JEDI" }
  })
});

export default withCharacter(({ data: { loading, hero, error } }) => {
  if (loading) return <div>Loading</div>;
  if (error) return <h1>ERROR</h1>;
  return ...// actual component with data;
});

Previous: Interacting with cached data
Next: Testing React components