How to Migrate a React App to TypeScript
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.
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
- Start simple
- Convert all files
- Increase strictness
- Clean it up
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 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
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 an SVG component that looks like this:
import React from 'react' const Spinner = () => ( // By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL <svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" > <defs> <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a"> <stop stopColor="#00a6f3" stopOpacity="0" offset="0%" /> <stop stopColor="#00a6f3" stopOpacity=".631" offset="63.146%" /> <stop stopColor="#00a6f3" offset="100%" /> </linearGradient> </defs> <g fill="none" fillRule="evenodd"> <g transform="translate(1 1)"> <path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" strokeWidth="2" > <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="0.9s" repeatCount="indefinite" /> </path> <circle fill="#00a6f3" cx="36" cy="18" r="1"> <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="0.9s" repeatCount="indefinite" /> </circle> </g> </g> </svg> ) export default Spinner
To properly convert this, we need to do two things:
- Change the file extension to
- Add the type annotation
Since this component takes no props, the only thing we need to change is this:
import React from 'react' + const Spinner: React.FC = () => ( //...the rest of the code )
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
Here are some other things to keep in mind as you do this:
- Suppress TypeScript errors by adding
// @ts-ignoreon the line before the error
- If a file uses jsx (i.e.
<App />), the file extension must be
- 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.
Now we are ready to increase the strictness by enabling stricter rules in our
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:
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:
which also enables these strict options:
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:
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.
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.
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
@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.