
Using Apollo Client with React to make GraphQL queries
This article will walk through the initial setup of a React SPA (single page application). The app will communicate with a GraphQL endpoint using the Apollo Client. We’ll use React Router within our project to help demonstrate some important fundamentals.
The article will cover some of the basic concepts of GraphQL (what it is, why you need it) — so if you aren’t already familiar, don’t worry.
You don’t need your own GraphQL API to follow along with the guide, we’ll use the GitHub GraphQL API.
Now, let’s begin!
The initial setup
Let’s get a basic app up and running. If you’re familiar with this part, feel free to skip right over it.
Create the app with create-react-app
We’ll be using create-react-app to quickly get up and running. This tool provides a fantastic starting point out of the box, meaning we can bypass a lot of configuration and bootstrapping to begin with.
Create a new app with:
npx create-react-app YOUR_APP_NAME --template typescript
I’m using TypeScript here, so I’ve opted for the TypeScript template.
Now navigate to your new directory and:
npm start
You should see the newly created app running on localhost:3000, now. Great!
Install the required packages
There are some extra packages we’ll be making use of. Let’s install all of those before we dig into any coding. We’ll want the React Router and the Apollo Client packages:
npm install react-router-dom
npm install @types/react-router-dom
npm install @apollo/client graphql
Add some routing
This starting app will only require two routes to begin with.
One route (/
) will be responsible for listing out the basic information associated with the repository we have searched for, along with a list of the most recent pull requests from within this repository.
The second route (/pullrequest/:id
) will handle a “detail” view for each pull request item. We’ll pass in the pull request ID here so we know which item we need to display in each case.
The purpose of the routing here is to simply help demonstrate the caching capabilities of Apollo Client.
Let’s block these routes in:
// App.tsx
const App = () => {
return (
<Router>
<div>
<header>React Apollo Client Example</header>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/" exact>
<LatestPullRequests />
</Route>
<Route path="/pullrequest/:id">
<PullRequest />
</Route>
</Switch>
</div>
</Router>
)
}
You can also see both of the components we’ll need referenced here — LatestPullRequests
and PullRequest
.
Introducing the Apollo Client
Now we want to configure the Apollo Client and make use of it within our app to query GitHub’s public GraphQL API.
Do I need Apollo (or some other library) to do this?
Absolutely not. You don’t need to use Apollo. But you probably should use it (or a similar solution). You can construct your own GraphQL requests as you see fit, there is nothing inherently wrong with this.
However, you’ll miss out on a lot of the benefits of using a library such as Apollo Client.
To briefly summarise these benefits, Apollo Client has:
- Built in caching
- State management mechanisms
- Useful hooks (more on these below)
It’s also really easy to set up and use. There are no downsides here, you’ll get a lot of value from utilizing this solution.
Configure the client
The first step is to create a new ApolloClient object:
// client.ts
import { ApolloClient, InMemoryCache } from "@apollo/client"
export default new ApolloClient({
uri: "https://api.github.com/graphql",
headers: {
Authorization: "Bearer YOUR_TOKEN"
},
cache: new InMemoryCache()
})
The client configuration is fairly standard.
We just need to provide the endpoint we want to fetch our data from as well as an authentication token (more on this shortly).
We’ll also initialize the InMemoryCache
. I won’t cover this in too much detail for now, but you should know that this is responsible for caching the results of our queries.
This is an important and powerful feature and one of the (many) benefits to using Apollo Client. The caching mechanism will be demonstrated in due course.
But wait.. where’s my token?
You’ll need a personal access token to pass to the authentication header when configuring the client.
You can generate one of these directly from your GitHub account by going to Settings -> Developer Settings -> Personal access tokens. Create a new token and then copy it over into your app.
Note: never commit these kind of tokens to GitHub! You should instead use environment variables to avoid storing sensitive information in your app. I won’t go into details here, but you should look at using a solution such as dotenv and/or take a look through this create-react-app adding custom environment variables article.
Add the ApolloProvider
With the client created, the last thing you’ll need to do is wrap your entire app with the Apollo Provider, passing in our client object:
// index.tsx
import { ApolloProvider } from "@apollo/client"
import client from "./graph/client"
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>,
document.getElementById("root")
)
This means the components within your app can now make use of the Apollo Client itself via its provider.
Our first Apollo Query
So we now have a very primitive app with some navigation in place. Let’s write our first query!
With Apollo Client, there are two main hooks you can use to query your API with:
- useQuery – executes immediately once the underlying component is mounted
- useLazyQuery – executes when invoked specifically
So useQuery is used when you need your data “on load”, right away. useLazyQuery is used when you want to ask for data upon a specific interaction in your app, for instance; a user opening a modal, or submitting a form.
In our starter app, we’ll use useQuery within our first component, LatestPullRequests
, it’ll look something like this:
const { loading, error, data } = useQuery(FETCH_REPOSITORY_WITH_ISSUES)
The actual query we are referencing here looks like this:
// operations/queries.ts
import { gql } from "@apollo/client"
export const FETCH_REPOSITORY_WITH_ISSUES= gql`
query FetchRepositoryWithIssues {
repository(owner: "facebook", name: "react") {
id
nameWithOwner
description
url
pullRequests(last: 10) {
nodes {
id
title
createdAt
bodyText
number
permalink
state
author {
login
}
}
}
}
}
`
So we’re attempting to fetch some data from Facebook’s React repository. We want some basic information about the repository, along with the 10 most recent pull requests.
Breaking down the useQuery call
And there we have it.
Loading or refreshing your app should now trigger a POST request to the GraphQL API. There are a few things to unpack here, let’s step through them in turn.
Loading, error, data…
The useQuery hook handles all of the various states we may need to watch out for. This makes it really convenient for us to react to these states within the app, and allows us to keep our code concise. Simply handle each case (loading, error returned, data retrieved successfully) as you wish.
The query itself
We’re using a standard GraphQL query. If you’re unfamiliar with GraphQL in general, it’s definitely worth acquainting yourself. The GraphQL page on queries and mutations is a great place to start.
To briefly summarise how a GraphQL API works, though: there is one endpoint only, in our case, graph.api.github.com. In our requests, we specify the exact data we want. The GraphQL API only returns this data for us to use.
Fetching data with Apollo Client in your React app is as simple as that. Neat, right?
You can check out the GitHub GraphQL API documentation to see what other fields you can include in your query requests.
Building out a simple page
Now we have a functional request, we can build out a simple page based on this returned data. Here’s the LatestPullRequests
component:
// LatestPullRequests.tsx
const LatestPullRequests = () => {
const { loading, error, data } = useQuery(FETCH_REPOSITORY_WITH_ISSUES)
if (loading) return <h3>Loading...</h3>
if (error) return <h3>Error :(</h3>
const repository: any = data.repository
const pullRequests: any[] = data.repository.pullRequests.nodes
return (
<>
<h1>{repository.nameWithOwner}</h1>
<h3>{repository.url}</h3>
<p>{repository.description}</p>
<ul>
{pullRequests.map((pr: any, index: number) => {
return (
<li key={index}>
<Link to={`/pullrequest/${pr.id}`}>
{pr.title}
<br />
{pr.author.login}
</Link>
</li>
)
})}
</ul>
</>
)
}
So I’m firstly querying the GraphQL API, as explained above. Then I’m displaying the basic information about the repository, as well as a list of the 10 most recent pull requests within this repository.
You’ll notice I’m linking to our second component, PullRequest
, this will allow us to see a clear demonstration of how the Apollo Client caching works.
We’ll construct the contents of this page shortly, but first, let’s touch on Apollo’s caching mechanism.
Caching with Apollo Client
One of the great things about using a library such as Apollo Client is that there is a caching mechanism in place under the hood. This requires basically zero configuration from the get-go.
The result of all of the queries you make are cached automatically. This means we can cut down on unnecessary network requests within our app — and we can also manually interact with this cached data in any way we please.
If you don’t have it already, I’d highly recommend installing the Apollo Client Dev Tools Chrome extension. This will give you some visibility on what is actually cached in your app, amongst other things.
Either way, lets move on and explore the effects of this caching mechanism.
Verify useQuery doesn’t trigger again
The first thing you can do is navigate away from our first page. So click on a link to one of the pull request detail pages. Now go back to the original page (where our useQuery hook is situated) — but keep an eye on the network tab.
You’ll notice that there is no subsequent request, but you can still see the repository data. This is because the data got cached as a result of our very first useQuery call, earlier on.
If you open the Apollo Dev Tools and navigate to the Cache tab, you’ll see data representing this, too.
In short: Apollo Client knows that we’ve already made this exact request, so it won’t execute the request again, it’ll use the cache first and foremost.
It’s important to note that this is the default behaviour of useQuery – it can of course by subverted should you choose. You can skip caching and always fire off network requests, for instance.
Manually interacting with the cache
There will often be times where you’ll want to interact with cached data manually. In our example, we want to show pull request details on our PullRequest
page.
As you can see — we’ve already fetched this data. You can inspect the Apollo Dev Tools Cache tab to verify this, also.
But we need to read the data and present it to the user.
Our second page looks a little bit like this:
const PullRequest = (props: any) => {
const params: any = useParams()
const pullRequest = useApolloClient().readFragment({
id: `PullRequest:${params.id}`,
fragment: gql`
fragment FetchedPullRequest on PullRequest {
title
number
createdAt
state
permalink
author {
login
}
}
`
})
if (!pullRequest) return <h1>PR not found :(</h1>
return (
<>
<h1>
{pullRequest.title} (#{pullRequest.number})
</h1>
<span>
{pullRequest.createdAt} by {pullRequest.author.login}
</span>
<span>{pullRequest.state}</span>
<a href={pullRequest.permalink}>View in GitHub</a>
</>
)
}
Let’s unpack this.
Using readFragment
In terms of reading cached data, there are two important methods that we need to be aware of:
- readQuery – grab the cached data for a specific query
- readFragment – grab the cached data for any object, regardless of query
These are the two functions that can be used to read directly from the cache. They are significantly different in how they should be used.
In short, if you want to retrieve the cached data that relates to a specific query, use readQuery.
If you want a specific piece of data, and you don’t care what query actually retrieved the data, use useFragment.
So in our case, useFragment is the function we need. When on the individual PullRequest page, we don’t want to grab the repository data & pull requests again. We just want a specific PullRequest.
We only need the unique ID and the type of the object, and you can see we are referencing both of those things (PullRequest:${params.id}
). Then we can use the useApolloClient
hook along with useFragment
as shown.
We now have all of the PullRequest detail data we need, directly from the cache!
But wait… What if my data is not cached?
This is a valid concern, and it’s something that should be handled.
If you refresh your browser when on a PullRequest
detail page, you’ll likely see an error. This is because the Apollo Cache is cleared on refresh.
In this case, it should simply be a case of firstly checking the cache. If we don’t find the data we are looking for — fire off a new request using useLazyQuery
.
This means that however the user reaches this specific page, there will always be some data to render out. Hopefully the data is cached and that can be used instead, if not – our secondary request will handle that for us.
To summarize Part 1…
We now have a functional app that can be used to fire off queries to our GraphQL endpoint. We can see Apollo’s caching mechanism in play.
Next time, we’ll look at fleshing out this app, and we’ll go into more detail on some of the GraphQL fundamentals. We’ll also cover some other neat features that Apollo Client has to offer us.
So stay tuned for that!