Apollo Client 3
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.
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…
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:
- 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 usingviewer
is the same viewer as before (and for our purposes, is always the same). - 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 thecreatedOn
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.