Apollo getCacheKey() with TypeScript

I’m currently working on the front end of an app that uses React + Apollo (via apollo-boost) + TypeScript. I’m to the point where I’m retrieving items from the cache in my resolvers using fragments1, which means I need to use getCacheKey() to search for the item's cache key using its ID field. (In this app, it would probably work to just assume the cache key is TypeName:UUID, but you never know, so we should probably do things properly.)

Unfortunately, I have not been able to find the correct typings for getCacheKey(). When passing in cache as an argument to a resolver, one can import NormalizedCacheObject for the typings, like so:

import { ApolloCache } from 'apollo-cache'
import { NormalizedCacheObject } from 'apollo-boost'

...

Mutation: {
  resolveSomeStuff: (
    _: undefined,
    { itemId, itemOptions }: { itemId: string; itemOptions: Array<string> },
    { cache }: { cache: ApolloCache<NormalizedCacheObject> }
  ) => {
  /* Now we resolve everything. Global warming, world
  hunger, over- and under-population.... */
  ...
  }
}

Inside my resolver, the Apollo docs tell me to get an item's cacheKey like this:

const cacheKey = cache.getCacheKey({ __typename: 'Item', id: itemId})

Unfortunately, if I use this formulation, tslint will yell at me:

"Property 'getCacheKey' does not exist on type 'ApolloCache'."

Well.

Half an hour of Googling was unhelpful. But eventually I found someone reporting a similar-sounding problem as an issue over at the apollo devtools repo. I adopted his workaround, and it was effective for me. So here is the code inside my resolver:

const cacheKey = cache['config'].dataIdFromObject({
  __typename: 'Item',
  id: itemId
})

This essentially does an end run around the problem by not calling getCacheKey() at all. I don't know enough about the inner workings of the Apollo client to understand why this works, but I kind of don't care.


If you don’t know what some or all of that means, this post probably isn’t for you.↩︎

Google Tag Manager in Next.js

The app I'm currently working on is bootstrapped using Next.js and needs to implement Google Tag Manager. The task was assigned to me, but Google's "Quick Start Guide" for GTM wanted me to do this:

"Copy the following JavaScript and paste it as close to the opening tag as possible on every page of your website, replacing GTM-XXXX with your container ID:"

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]|[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.jsid='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-XXXX')</script>
<!-- End Google Tag Manager -->

And also this:

"Copy the following snippet and paste it immediately after the opening tag on every page of your website, replacing GTM-XXXX with your container ID:"

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

If you know a little more about React development than I do after four months, you can instantly spot that following these instructions very literally is going to require you to use dangerouslySetInnerHTML to force React to use this inline script in the <head> of the page. The reason this attribute is named so verbosely and alarmingly is that incautious use of it opens a site up to a cross-site scripting attack.

I didn't know this, so I had to spend five minutes searching to find it out. Still, when I did, I immediately started looking around for a more React-friendly alternative. (Google's own docs were strangely silent on the issue.) Further searching produced this module, but its examples weren't very helpful:

import React from 'react'
import ReactDOM from 'react-dom'
import Router from 'react-router'
import routes from './routes'

...
import TagManager from 'react-gtm-module'

const tagManagerArgs = {
  gtmId: 'GTM-000000'
}

TagManager.initialize(tagManagerArgs)
...

const app = document.getElementById('app')
ReactDOM.render(<Router routes={routes} />, app)

When you read documentation for almost anything based on React you see things like this all the time, and when you're an Extremely Junior DeveloperTM it can be very frustrating because you never mount an app the way those last two lines suggest when you're using the common bootstrappers like create-react-app or Next.js. Since you only barely understand how any of this works, you can feel very uncertain about how to adapt this code to your own actual app. And what are those ellipses hiding? I'd like to see that code, please.

So from my experience as that Extremely Junior Developer, this just looked like we were tossing an initialize method into a random location where it would never get called. I had to dig around for quite some time to find this Next.js GitHub issue that provided a helpful example, not for Google Tag Manager, but for Google Analytics. Fortunately, the poster explicitly said that it would work for "other ga tools"

My needs were slightly simpler than the poster's, so here's the code I ended up with ("XXXXXXX" here represents whatever your GTM ID might be), in the _app.js file of my NextJS installation:

import App, { Container } from 'next/app'
import React from 'react'
import './app.css'
import TagManager from 'react-gtm'

const tagManagerArgs = {
  id: 'GTM-XXXXXXX'
}

class MyApp extends App {
  componentDidMount () {
    TagManager.initialize(tagManagerArgs)
  }

  render () {
    const { Component, pageProps } = this.props
    return (
      <Container>
        <Component {...pageProps} />
      </Container>
    )
  }
}

export default MyApp

A couple notes:

  1. If you don't have an _app.js file in your Next app, here's what that's all about.
  2. I didn't actually end up using the module I mentioned above, but this fork of it, which seemed like it was a little more up-to-date.

Hopefully this helps some young developer who felt just as lost as I did when I was trying to solve this problem.

gatsby-plugin-favicon

If you need to add a favicon to a Gatsby site, there’s an existing plugin: gatsby-plugin-favicon. I installed it this morning and was able to see the favicon in develop mode (haven’t tried it with a build or deploy yet). BUT FIRST I had to get past what seemed like a dealbreaker: Running gatsby develop with the plugin installed caused the terminal to hang at info bootstrap finished.

I verified that it was the plugin causing the issue and actually uninstalled it before realizing what the problem was: Illustrator had output my .png at a much higher resolution than I realized. The gatsby-plugin-favicon documentation recommends an image of 1500x1500 pixels (from which it will create all the appropriate icons for various devices), but I was trying to make it process a 5000+px image. I had also forgotten to make it completely square; Illustrator cropped the export to the bounding box, not the artboard.

Scaling the image down to a nice square 1500px resolved the issue.