profileRyan KesPGP keyI build stuffEmailGithubTwitterLast.fmMastodonMatrix

Use TypeScript Type Assertions To Override Incorrect Interface Definitions

Sometimes when using external libraries you will find that interfaces aren't correctly defined. This happened to me today using React Helmet. The Helmet class has the following definition:

export class Helmet extends React.Component<HelmetProps> {
  static peek(): HelmetData  static rewind(): HelmetData
  static renderStatic(): HelmetData
  static canUseDOM: boolean
}

export interface HelmetData {
  base: HelmetDatum
  bodyAttributes: HelmetHTMLBodyDatum
  htmlAttributes: HelmetHTMLElementDatum
  link: HelmetDatum
  meta: HelmetDatum
  noscript: HelmetDatum
  script: HelmetDatum
  style: HelmetDatum
  title: HelmetDatum
  titleAttributes: HelmetDatum
}

However, running console.log(Helmet.peek()) returns the following output:

{
  "baseTag": [],
  "bodyAttributes": {},
  "defer": true,
  "encode": true,
  "htmlAttributes": { "lang": "en" },
  "linkTags": [],
  "metaTags": [    { "name": "description", "content": "passed description" },    { "name": "twitter:card", "content": "summary" },    { "name": "twitter:creator", "content": "author" },    { "name": "twitter:title", "content": "site title" },    { "name": "twitter:description", "content": "passed description" }  ],  "noscriptTags": [],
  "onChangeClientState": [Function(anonymous)],
  "scriptTags": [],
  "styleTags": [],
  "title": "passed title | site title",
  "titleAttributes": {}
}

This is annoying as I have to access the metaTags property to write unit tests (see highlighted lines above). Running the following code

const helmet = Helmet.peek()
console.log(helmet.metaTags)

causes TypeScript to throw TS2339 errors (see below) as according to the interface metaTags doesn't exist

TS2339: Property 'metaTags' does not exist on type 'HelmetData'.

So what is to be done? Luckily in TypeScript one can use type assertions. According to the documentation:

Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does. Usually this will happen when you know the type of some entity could be more specific than its current type.

Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.

The quick fix to prevent TS2339 errors in this case is to use the following code snippet:

interface RealHelmetData extends HelmetData {
  metaTags: [
    {
      name: string
      content: string
    }
  ]
}

const helmet = Helmet.peek() as RealHelmetData

Pretty basic stuff but hopefully this will help someone else out who encounters this issue. I might write a short tutorial on how to properly unit test Helmet with Jest in the future.