Skip to main content

How to Migrate a React App to TypeScript

By Joe Previte

JavaScript

Share:

Free JavaScript Book!

Write powerful, clean and maintainable JavaScript.

RRP $11.95

When I first started learning TypeScript, one of the suggestions I often heard was, “convert one of your existing projects! It’s the best way to learn!” Soon after, a friend from Twitter offered to do just that — show me how to migrate a React app to TypeScript.

The purpose of this article is to be that friend for you and help you migrate your own project to TypeScript. For context, I will be using pieces from a personal project which I migrated while going through this process myself.

The Plan

To make this process feel less daunting, we’ll break this down into steps so that you can execute the migration in individual chunks. I always find this helpful when taking on a large task. Here are all the steps we’ll take to migrate our project:

  1. Add TypeScript
  2. Add tsconfig.json
  3. Start simple
  4. Convert all files
  5. Increase strictness
  6. Clean it up
  7. Celebrate

NOTE: the most important step in this whole process is number 7. Although we can only get there by working through them in sequential order.

1. Add TypeScript to the Project

First, we need to add TypeScript to our project. Assuming your React project was bootstrapped with create-react-app, we can follow the docs and run:

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

or if you’re using yarn:

yarn add typescript @types/node @types/react @types/react-dom @types/jest

Notice we haven’t changed anything to TypeScript yet. If we run the command to start the project locally (yarn start in my case), nothing should be different. If that’s the case, then great! We’re ready for the next step.

2. Add the tsconfig.json

Before we can take advantage of TypeScript, we need to configure this via the tsconfig.json. The simplest way for us to get started is to scaffold one using this command:

npx tsc --init

This gets us some basics.

We have not yet interacted with TypeScript. We have only taken the necessary actions to get things ready. Our next step is to migrate a file to TypeScript. With this, we can complete this step and move onto the next.

3. Start with a Simple Component

The beauty of TypeScript is that you can incrementally adopt it. We can start with a simple component for our first piece of this migration. For my project, I’m going to start with a Button component that looks like this:

import React from 'react'
import { buttonStyles } from './Button.styles'

const Button = ({ onClick, text }) => (
  <button onClick={onClick} className={buttonStyles}>
    {text}
  </button>
)

export default Button

To properly convert this, we need to do two things:

  1. Change the file extension to .tsx
  2. Add the type annotation

Since this component takes two props, we need to change a few things:

import React, { MouseEventHandler } from 'react'
import { buttonStyles } from './Button.styles'

type Props = {
  onClick: MouseEventHandler,
  text: string,
}

const Button = ({ onClick, text }: Props) => (
  <button onClick={onClick} className={buttonStyles}>
    {text}
  </button>
)

export default Button

Let’s double-check that things are still working by running the project to ensure we didn’t break anything. Notice, here react-scripts will automatically detect the new changes and modify our tsconfig.json for us! Voila! How beautiful is that?

And if all is well, our project will remain in working condition. Give yourself a pat on the back! You’ve successfully migrated your first file to TypeScript. If we wanted to stop here, we could, but let’s push forward.

4. Convert All Files

The next step is to do what we did for step 3, but for all files in the project. If the project you’re migrating is rather large, I suggest doing this over multiple iterations. Otherwise, you may tire yourself out.

During this step, you may need to add additional packages depending on what third-party libraries you’re using. For instance, I am using moment so I had to run yarn add -D @types/moment to add the types as a devDependency.

Here are some other things to keep in mind as you do this:

  • Suppress TypeScript errors by adding // @ts-ignore on the line before the error
  • If a file uses jsx (i.e. <App />), the file extension must be .tsx instead of .ts
  • Run the project locally to make sure things are still working (they should be)

After you’ve completed this step, the hard stuff is done! Our project will be in TypeScript, but we’ll need to increase the strictness to take advantage of the benefits.

5. Increase tsconfig.json Strictness

Now we are ready to increase the strictness by enabling stricter rules in our tsconfig.json. Thankfully, react-scripts will inform us of any type errors while running our project locally. We will follow the process like so:

  1. enable rule
  2. start project locally
  3. fix errors

And we will repeat this process for the following rules:

  • "noImplicitAny": true
  • "strictNullChecks": true
  • "noImplicitThis": true
  • "alwaysStrict": true

I want to share a tip. If you find that something implicitly has the type any and you’re not sure how to fix it in that moment, don’t. Create this and use it to hush the error:

export type FixMeLater = any

Our goal is to move forward quickly and go back later to fix these.

This will bring a greater amount of type safety to our project. If you’d like to read more about compiler options, you can read about it in the TypeScript Handbook.

Once we’ve done this, we can then replace these:

  • "noImplicitAny": true
  • "strictNullChecks": true
  • "noImplicitThis": true
  • "alwaysStrict": true

with this:

  • "strict": true

which also enables these strict options:

  • strictBindCallApply
  • strictNullChecks
  • strictFunctionTypes
  • strictPropertyInitialization

At this point, we have reached a standard level of strictness in our project. If we want to add additional checks, we can add these rules:

  • "noUnusedLocals": true
  • "noUnusedParameters": true
  • "noImplicitReturns": true
  • "noFallthroughCasesInSwitch": true

Once we’ve reached a level of strictness that we’re happy with, we can proceed to the next step.

6. Clean Up Shortcuts

If you added @ts-ignore or took advantage of a FixMeLater type, now is the time to go back and fix them. We don’t have to do these all at once, or ever, but this would be the last step to ensure maximum type safety across your project.

Sometimes the effort to fix these is not worth the time, and other times it is. You’ll have to discuss with your team and decide what makes sense.

7. Celebrate

We did it! We officially migrated our project to TypeScript. Take a moment to celebrate your work. It was certainly not a trivial task. Especially if you were working in a large codebase.

Things to Remember

As we reflect on our efforts, here are some things to remember when migrating a project from React to TypeScript.

Start Small

Take advantage of TypeScript’s ability to gradually adopt it. Go one file at a time at your own pace. Do what makes sense for you and your team. Do not try to tackle it all at once.

Increase Strictness Over Time

There is no need to start with maximum strictness from the beginning. It is a journey. Increase the level as you have time. Eventually, you will reach a level that feels comfortable. Do not feel bad if you do not have 100% strictness. Some type safety is better than no type safety.

Lean on Shortcuts

The @ts-ignore and the tip for FixMeLater are there to help lessen the burden of the migration. Not everything needs to changed at once. Use the shortcuts as you need but do not feel bad for using them. Again, the point is to migrate, but it should not be painful. Over time, you can prioritize replacing these things with proper type safety. But remember, these tools are at your disposal so use them.

This is not the only approach for migrating React projects to TypeScript. However, it is what works for me. I hope it helps you as much as it helped me.

Further Reading

Updated on June 10, 2020:
Special thanks to Karl Horky who reached out explaining that the `React.FC` type is not recommended because it provides almost no benefit and it has some downsides. See this GitHub discussion for more information.

Joe Previte is a developer, a teacher and creator of Vim for VSCode. In his spare time, he enjoys creating videos for egghead, writing articles relating to coding and leading an online meditation study group.

New books out now!

Learn how Git works, and how to use it to streamline your workflow!


Google, Netflix and ILM are Python users. Maybe you should too?