Paul Cook,
Back

TypeScript

The Great Gatsby: Using TypeScript

I find there are few middle of the road opinions on TypeScript. You either love it and can’t imagine writing vanilla JavaScript anymore. Or you think the idea of types in JavaScript mars its sublime dynamic beauty.

I used to be in the camp of people who don’t see the point of TypeScript. I was writing POJS (plain old JS) fine without it!

And then I wrote TypeScript every day for 2+ years, and now when I go back to vanilla JavaScript I cry into my keyboard.

But this post isn’t about convincing you to use TS. You do you and write all the untyped JS to your heart’s content.

I’m gonna be over here writing my types and converting my Gatsby blog to use TypeScript.

Let’s install some stuff

One of the great parts of Gatsby is its plugin ecosystem. At last check on gatsbyjs.org/plugins there’s over 800 on there right now.

One of those 800+ plugins is gatsby-plugin-typescript, and that’s what we’ll use to write our Gatsby site in TS.

There are a few steps we’ll take:

  1. Install the plugin
  2. Include the plugin in our gatsby-config.js
  3. Create a tsconfig.json
  4. Rename our components to use .tsx
  5. ???
  6. Fix our types up
  7. Chill

We can cut out steps 5 and 7 and keep this to a 5 step process.

Step one: Installing

Run this:

npm install gatsby-plugin-typescript

Step two: Add it to our config

This is the gatsby-config.js file at the root of our site.

All we’re gonna do there is add our TS plugin in the plugins array:

plugins: [
// leave the other plugins there!
`gatsby-plugin-typescript`,
]

Step three: Create a tsconfig.json

We need to add a tsconfig.json file to the root of our project. What that does is allows us to specify options/rules for compiling our project and what files it should compile.

Here’s ours:

{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"jsx": "preserve",
"lib": ["dom", "es2015", "es2017"],
"strict": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": false,
"preserveConstEnums": true,
"strictNullChecks": true,
"noImplicitAny": true
},
"include": ["./src/**/*"]
}

This is all pretty boilerplate, and most of it from the project Gatsby TypeScript Starter except for the strictNullChecks and noImplicitAny.

The TypeScript team has this to say:

As a note: we encourage the use of --strictNullChecks when possible, but for the purposes of this handbook, we will assume it is turned off.

TS Handbook Basic Types

We’ll follow that convention as I've cried many tears over null checks I should have made but didn’t.

The noImplicitAny prevents us from using the escape hatch of the any type.

Again, from the TS Handbook:

We may need to describe the type of variables that we do not know when we are writing an application. These values may come from dynamic content, e.g. from the user or a 3rd party library. In these cases, we want to opt-out of type checking and let the values pass through compile-time checks. To do so, we label these with the any type,

It’s fine if you want to use any, but I like to try not to. I find that if I’m doing TypeScript right I should know the types of all the data in my application. That’s a powerful thing.

We can always turn it off later if it becomes a problem.

Renaming to .js to .tsx

This part is pretty easy. We’ll be renaming our files from using .js to using .tsx.

We want to rename any .js files in the src/ folder. Anything above that won’t be compiled by TypeScript. We made sure of that with this property in our tsconfig.json:

"include": ["./src/**/*"]

Step five: Fixing ye olde types

For most of the files we shouldn’t have any problem, but in some of them, we will begin to see errors from the types.

Let’s go ahead and fix that.

Deleting prop types

With TypeScript we don’t need React’s PropTypes, as, we get all of that from the compiler.

So, anywhere you see PropTypes we should delete it, and provide our own interface that matches the shape of those PropTypes.

As an example:

header.tsx

import { Link } from "gatsby"
- import PropTypes from "prop-types"
import React from "react"
- const Header = ({ siteTitle }) => (
+ const Header = ({ siteTitle = `` }) => (
<header
style={{
background: `rebeccapurple`,
</header>
)
- Header.propTypes = {
- siteTitle: PropTypes.string,
- }
- Header.defaultProps = {
- siteTitle: ``,
- }
export default Header

We don’t need to create an interface here for the props as we can either specify the type like this:

const Header = ({ siteTitle: string }) => (

Or go ahead and specify a default argument of a blank string since that’s what the defaultProps did.

With that, we can delete any references to PropTypes.

Creating interfaces where they’re needed

Interfaces in TypeScript help us define the shape of our code. The compiler will then ensure that our code follows that defined shape.

Let’s look at an example with our SEO component.

This is what our propTypes looked like:

seo.tsx

SEO.defaultProps = {
lang: `en`,
meta: [],
keywords: [],
description: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
keywords: PropTypes.arrayOf(PropTypes.string),
title: PropTypes.string.isRequired,
}

A lot of default props and prop types.

Of course, we’re getting rid of any prop types so let’s go ahead and replace that with a TypeScript interface.

seo.tsx

interface Props {
description?: string
lang?: string
meta?: Array<{ name: string; content: string }>
keywords?: Array<string>
title: string
}

We’ll keep it simple and name it Props.

Notice the ? after a lot of the property names description?: string.

All that does is specify it as an optional property. That is when we have an object that conforms to the Props interface it does not have to have a description, lang, meta, or keywords.

I didn’t do that for any reason other than to satisfy the compiler, and that’s also the shape of the old propTypes object.

The only required field is the title.

One other thing to note is that we don’t have defaultProps, and since an interface doesn’t exist at runtime we have to go ahead and define our default arguments. If we don't the compiler will get cranky at us.

seo.tsx

function SEO({
description = ``,
lang = `en`,
meta = [],
keywords = [],
title,
}: Props) {}

Adding external types

There is at least one case where we need to import external types.

Let’s take a peek at the layout.tsx file. We’ll go ahead and clean up the references to propTypes in this file, but one thing you’ll notice is the structure of it:

Layout.propTypes = {
children: PropTypes.node.isRequired,
}

We have an object with a children prop that is a node.

In this case, we’ll need to go ahead and add some external types to help us properly type the props for this component.

npm install @types/react @types/react-dom @types/react-helmet

This will give us all the type definitions we’ll need.

Once that finishes installing we’ll start putting the typings to work.

layout.tsx

import React, { ReactNode } from "react"
import { StaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
interface Props {
children: ReactNode
}
const Layout = ({ children }: Props) => ( ... )

Side note: we imported ReactNode up there alongside React. If we hadn’t done that the interface would have looked like this:

interface Props {
children: React.ReactNode;
}

One cool part about types is they provide implicit documentation about the program. What I mean is that it helps other people coming behind you know exactly what the program is expecting.

Other info

There’s a lot of cool resources out there for TypeScript out there.

I’ll point out a few:

Like I’ve said before, I’m not looking to wade into the static vs. dynamic typing wars. I happen to like TypeScript and find a lot of value in it.

If you don’t, fine! You do you.

Credit where credit is due

Leigh Halliday has an excellent screencast where he walks through the same steps. If anything from this post is unclear for you, I recommend you:

  1. Let me know what it is, so, I know I need to work on it
  2. Check out Leigh’s video here: https://www.youtube.com/watch?v=ji2TtRVlNDQ

If that video is any indication I would say his other stuff is worth checking out, as well.