Apollo Client 3

Lexi Smith
Catch

--

A few days ago, Apollo announced a shiny new Apollo Client. Here at Catch, we use Apollo as the library to interface with GraphQL and therefore, all of our data, making it one of the most important libraries in our technical stack.

By the way, if you’re a developer, I highly suggest following technologies you use on Twitter/Reddit/etc. New releases, features, and tutorials will magically appear in your timelines for your perusal 😃

You can read their blog announcement here, but the gist of it is:

  • Simplified dependencies/imports
  • Updated cache APIs
  • More reactivity

We were excited for all of this (‼️) and started the migration process, which I’ve detailed for you below.

Migrating to Apollo Client 3

We followed along with the installation instructions from Apollo here.

#1 Adjusting our dependencies and imports

One of the promises of the new version is simplified dependencies. The first step here is adding the new Apollo client

Next, we went through our dependencies one by one to update them. We used several of the Apollo packages, so I’ll detail below what the process was like for each individually.

Our original Apollo dependencies in package.json

apollo-boost
This was not actually in use anywhere in our app, so we simply removed it from our package.json file. 🙄

apollo-client
We were importing the Apollo Client in one mainApollo.js file. This was as easy as changing the import to the updated package:

apollo-link and apollo-link-http
In that same file, we adjusted the link packages to come from the new @apollo/client library, then removed the old dependencies from our package.json file. The new Apollo client has options for uri headers and credentials directly on the client, if you don’t need a custom link. However, because we have some custom functionality for handling GQL errors, we kept our existing Link config.

apollo-link-*
The rest of the add-on link packages have been moved into the @apollo/client package as well. We use apollo-link-error and apollo-link-context for additional functionality on our link, so we converted them to their new respective homes (paths). We were then able to remove two more dependencies 💪

Note: the migration docs mentioned that “Apollo Client 3 no longer allows @client fields to be passed through a Link chain”. I made note of this, in case we needed to revisit this restriction.

graphql-tag
Instead of having to import gql from the graphql-tag package, it is now included in the base @apollo-client library. It’s almost as if gql is essential for using Apollo 😉. A quick cmd-shift-F for this import showed 288 results (essentially, every query and mutation definition) and we replaced them all with the updated import. Then, we were free to remove thegraphql-tag package 👋

react-apollo

I found this very interesting: the react-apollo package was deprecated and its functionality wrapped into Apollo 3. For folks using Vue or Angular, you can import the ApolloClient from @apollo/client/core instead. However, this move definitely speaks to React as a pseudo-default framework.

Back to our migration — I split this into a couple pieces:

Good 👍
We have two custom hooks — useQuery and useMutation — which use the Apollo hooks, but also implement some extra sauce like logging and formatting. Therefore, we only had to change each of these in one spot. Nice.

Bad 👎
Not all of our queries/mutations have been updated to use these hooks. Previously, we were using containers for these, so we had to find-and-replace all instances of the container-based queries/mutations.

Another one:
We almost forgot one more import coming from react-apollo The ApolloProvider now comes directly from @apollo/client as well. Make sure that for testing purposes, you’re using the MockedProvider @apollo/client/testing as well.

apollo-utilities, apollo-cache-persist
Not being used anywhere for us, simply removed these.

apollo-cache-inmemory
As you may have guessed by now, this has also been moved to @apollo/client proper. However, we ran into a bit of a hiccup here, as there’s a note that some things in our config (fragment matcher, dataIdToObject) have been deprecated. So for now, I moved InMemoryCache and removed the deprecated pieces.

At this point, everything is using the new @apollo/client and we’re down from 13 different dependencies to only 1 dependency. Pretty cool 😎

#2 Moment of truth

Up to this point, I’ve just been replacing everything. I start the app locally, go to the home page, and unfortunately, this is what it looks like…

Yeah, this is not what’s supposed to be happening :/

I can see from the networks tab that we’re making all of the requests for the home page over and over again, plus we have some recurrent warnings in the log from Apollo that look like this:

Missing cache result fields: ...

We are able to narrow it down to two main issues:

  1. Our root query is viewer so all queries go through the viewer object. Since the viewer doesn’t have any ID, the cache has no way to know that the next query using viewer is the same viewer as before (and for our purposes, is always the same).
  2. For other queries that don’t have a unique ID, we need to specify with the cache how to identify it. For our Recommendations type, we can use the createdOn timestamp as the unique identifier.

We can resolve both of these issues using the typePolicies option when declaring our cache. For the root viewer, we can automatically merge all incoming/existing data in the viewer. For the recommendations, we provide the unique key for Apollo to use. It ends up looking like this:

and our home page is back to normal:

After this, I’ll be walking through our entire app to see if there are any more keyFields that should be added to our typePolicies

Conclusion

  • Pretty painless migration (thanks Apollo!)
  • Excited to make use of new features 💪
  • Nice to have our dependencies pared down, especially because it’s easier for us to see what we’re actually using.

--

--

Lexi Smith
Catch
Editor for

Engineering at Catch. Lifelong learner, aspiring Jeopardy contestant