Apollo Client + Typescript Hook Patterns

Kenny Hammerlund
Level Up Coding
Published in
4 min readApr 21, 2020

--

Photo by Steve Johnson on Unsplash

We have seen a lot of great new things out of React and Apollo in the last 18 months. Arguably, hooks are one of the most important changes. In my opinion, they have had the most impact on the structure, look, and feel of our codebases. I spent some time with another developer, Ben Lacy, hashing out how we wanted our hook structure to look and this is what we have found to be the most useful. This is part 1/3 and covers folder structure and useQuery. Look for useMutation and useLazyQuery articles soon.

Folder Structure

Previously

In a previous project that was GraphQL based we used a folder structure that looked like this.

app
- graphql
* query_name.gql
- hoc
* ApolloQueryHelper.js
- pages
* dashboard.js
- components
* ListItem.js
- types
* User.ts

There is an advantage to this structure, you can reuse queries and fragments easily. Queries should have unique names. This structure reduces duplication of gql queries that have the same signature but use a unique name. RefetchQueries uses query names therefore using a single query name is easier to track if your app needs to use the RefetchQueries function.

A better approach

If we start thinking more in modules we can create components that are less coupled to other parts of our app. The goal is to only have to pass in a single id to produce a component. This will create components that have localized display/logic and be easier to trace across the project. This will also allow you to port components to new projects with greater ease.

app
- components
- UserName
- graphql
- __generated__. <-- created by Apollo codegen
* generatedQueryType.ts <-- created by Apollo codegen
* useUserNameQuery.ts
* useUserNameMutation.ts
* UserName.tsx

Notice the generated files? I generally work in typescript and these are created by Apollo Codegen (https://github.com/apollographql/apollo-tooling) I highly recommend taking a look. The motivation here is to contain your display logic in UserName.tsx and move most of your business/controller logic to useUserNameQuery or useUserNameMutation

UseQuery

UseQuery is a hook used to control a graphql query. In Apollo client 3 its baked into the @apollo/client package. Query hooks are generally pretty straight forward.

import { useQuery } from "@apollo/client";
import gql from "graphql-tag";
import { ActivityTableQuery, ActivityTableQueryVariables } from "./__generated__/ActivityTableQuery";
export default () =>
useQuery<ActivityTableQuery, ActivityTableQueryVariables>(gql`
query ActivityTableQuery {
activity(order_by: { created_at: desc }) {
id
title
}
}
`);

Dissecting this a bit, our imports of useQuery and gql are standard. The next line import { ActivityTableQuery } from "./__generated__/ActivityTableQuery" is importing the generated type which we pass into the useQuery hook. This query does not have variables but if we did we would pass them in as the second type param. After that we pass in our gql query wrapped in the gql tag. An important thing to note is that your queries should use a unique name and anonymous queries are tabo. Anonymous queries are those that just open with a curly bracket instead of using query MyQueryName. The query name will be used to generate your types, I prefer capital type names therefore my queries get capital names.

Consuming

import React from "react";import useActivityQuery from "./graphql/useActivityQuery";...interface ActivityTableProps {...}const ActivityTable = (props: ActivityTableProps) => {
const { data, loading } = useActivityQuery();
return (
<ul>
{data?.activity?.map(act=> <li key={act.id}>{act.title}</li>)
</ul>
)
}

One of the benefits of GraphQL is that you can design your data the way you want. In our case we only wanted the title and id of our activities so that’s all we asked for.

If you have control over your schema I suggest building it in a BFF (Back-end for front-end) style. Building your types to be more representative of how the data will be displayed rather than types that look like your RDB structure will make everyone's lives easier and development move faster.

What If I don’t have control over the API/Graph?

This is a common issue when consuming 3rd party apis. How can we modify our hook to consume data and pass it into our display component in a way that is easily consumable?

Let’s say we have a union type: union Activity = SummerActivity | WinterActivity each uses a different primary key, summerId and winterId. The component we have only wants to display a single value id so this is how we could modify our hook.

import { useQuery } from "@apollo/client";
import gql from "graphql-tag";
import { ActivityTableQuery, ActivityTableQueryVariables } from "./__generated__/ActivityTableQuery";
export default () => {
const {data} = useQuery<ActivityTableQuery, ActivityTableQueryVariables>(gql`
query ActivityTableQuery {
activity(order_by: { created_at: desc }) {
... on WinterActivity{
winterId
}
... on SummerActivity{
summerId
}
}
}
`)
const id = activity.__typename === 'SummerActivity' ?
activity.summerId
: activity.__typename === 'WinterActivity' ?
activity.winterId
: null
return { id }
};

Now when we consume our data we we would only see an id regardless of the type.

This is part 1 of 3, in my next article we will explore useMutation and the interesting patterns used to contain mutating data.

--

--