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.
Key Takeaways
- Begin the migration of a React app to TypeScript by adding TypeScript to the project, adding tsconfig.json, starting simple, converting all files, increasing strictness, cleaning up, and celebrating the successful migration.
- It’s possible to adopt TypeScript incrementally, starting with a simple component. Convert the file extension to .tsx and add the type annotation. Gradually convert all files in the project.
- Increase the strictness of tsconfig.json by enabling stricter rules. This can be done gradually, fixing errors as they occur. The goal is to move forward quickly and return later to fix any issues.
- During the migration process, take advantage of shortcuts such as @ts-ignore and FixMeLater types to lessen the burden. Over time, prioritize replacing these shortcuts with proper type safety.
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:
- Add TypeScript
- Add
tsconfig.json
- Start simple
- Convert all files
- Increase strictness
- Clean it up
- 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:
- Change the file extension to
.tsx
- 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:
- enable rule
- start project locally
- 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
- React with TypeScript: Best Practices
- Practical Ways to Advance Your TypeScript Skills
- How TypeScript Makes You a Better JavaScript Developer
- JavaScript: Novice to Ninja, 2nd Edition
- React and React Native – Second Edition
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.
FAQ on Migrating a React App to TypeScript
TypeScript is a statically-typed superset of JavaScript that provides enhanced type checking, code quality, and tooling support. Migrating to TypeScript can help you catch errors at compile-time, improve code maintainability, and enhance the developer experience when working on a React app.
To get started, you can add TypeScript to your project using a tool like Create React App with TypeScript templates, or manually configure your project to support TypeScript. You’ll also need to rename your .js
and .jsx
files to .ts
and .tsx
, respectively.
The process typically involves:
a. Renaming your JavaScript files to TypeScript files with .ts
and .tsx
extensions.
b. Adding type annotations to your variables, functions, and props.
c. Resolving type errors and fixing any issues identified by the TypeScript compiler.
d. Leveraging TypeScript features like interfaces and enums to define data structures and constants.
Yes, you can migrate your app incrementally. You can start by converting one component or module at a time while maintaining the existing JavaScript codebase. This way, you can gradually transition to TypeScript without disrupting your development workflow.
Some of the benefits include better code quality, improved developer productivity, enhanced code auto-completion, more robust refactoring tools, and early detection of common errors. It also provides clearer and more self-documenting code through type annotations.
You can find TypeScript type definition files (usually with a .d.ts
extension) for many popular libraries on DefinitelyTyped or use tools like @types
. If type definitions are not available, you can create your own or use TypeScript’s any
type to work with untyped libraries.
Most popular code editors and IDEs like Visual Studio Code have excellent support for TypeScript. You can install TypeScript plugins and extensions for your editor to benefit from enhanced TypeScript integration, auto-completion, and error checking.
Common challenges include resolving type errors, dealing with third-party libraries that lack type definitions, understanding TypeScript’s type system, and adjusting to the stricter type checking that TypeScript enforces.
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.