Migrating from Apollo Client 2 to 3

Jonathan Suzuki
Qulture.Rocks Product Blog
4 min readMar 31, 2021
A rocket taking off making an arc in the sky
Photo by SpaceX on Unsplash

About 8 months ago apollo-client had a major release and we were excited to try out the new features especially the local fields and the reactive variables. While their migrating guide does an incredible job explaining new features and the reasoning behind the breaking changes, it didn't offer much help for those who liked and wanted to keep the old non-normalized merge inference. This is how we managed to migrate to version 3 while keeping our development flow the same.

The main breaking change is how apollo deals with nested objects without an ID. Before, it inferred an ID for those nested objects and would merge the results of other requests that queried it. For example, let's suppose we have in the same screen two queries for managing the notifications in our company: CompanySettingsForPraise and CompanySettingsForFeedback .

https://gist.github.com/JoSuzuki/b57864f04fc65ab5d949d65d2ec90478
https://gist.github.com/JoSuzuki/97e2fac3f1a4f83048d06c006da5a497

Previously apollo v2 would create a fake id for the nested field something like Company.1.CompanySettings , so if we queried CompanySettingsForPraise followed by CompanySettingsForFeedback, apollo would merge the settings, and we would have the cache for the company as the following:

https://gist.github.com/JoSuzuki/a0cade6668fe5d73e474bfd1bce299ee

However, this merge strategy wasn't always safe, so in v3, the default is to replace the previous result when no merge strategy is defined. The result of the last queries without any additional configuration would then be:

https://gist.github.com/JoSuzuki/8621c8640610fb930cf2c58e1dbe869e

⚠️ This was a problem for us since the place that used CompanySettingsForPraisewould be updated without the key praiseNotificationsEnabled it had asked for earlier!

For us to be able to merge them, like we used to, we have to declare in the cache a merge strategy. This can be achieved by declaring a typePolicy with merge: true.

https://gist.github.com/JoSuzuki/a7f235cfced352725bc0dbb105848a59

But that would mean that every new type we would have to remember (and I don't know you, but I forget things all the time) that it won't be merged by default and manually add the type to the cache configuration. This seemed like it would cause a number of unexpected and hard-to-track bugs as it is order dependent and will usually only impact longer sessions.

Hopefully, the Apollo team was aware of this difficulty and proposed an inheritance of typePolicies making it easier to extend multiple types at once. This means we can define a client-only supertype ; we chose the name MergeableTypes , leaving our configuration like this:

https://gist.github.com/JoSuzuki/3546f3559161611bca1eb2f81ba16651

Now we needed to discover how to define the mergeableTypesArray .

We were already using graphql-code-generator fragment-matcher to generate our IntrospectionFragmentMatcher , which was replaced by possibleTypes in v3. We saw a section about custom-plugins and decided to give it a try.

We wrote a very simple plugin that generates a typescript file with an array containing all the ObjectTypes and InterfaceTypes of our schema.

https://gist.github.com/JoSuzuki/0acca276a0aaa108e101be35dd02c218

We added the custom plugin to our codegen.yml

https://gist.github.com/JoSuzuki/c3790bff79989ab40a4b0035bbef70a0

And sure enough, now our types would behave just like in v2, merging the nested fields! The nice part is that if we ever need to overwrite the merge strategy for a specific field, we can just add it to the cache configuration. Our flow managed to stay the same, and we can now leverage the new features of local fields and reactive variables 🎉.

How was your experience migrating to v3? Doubts, comments, feedback? Leave it in the comments below!

--

--