Getting Started: Convert a React Project to TypeScript

As a superset of JavaScript, Typescript can work in conjunction with JavaScript, importing Typescript code into a JavaScript file and vice versa. This means that migrating to TypeScript can be done incrementally, far different from converting a codebase from one programming language to something unrelated.

However, it can be daunting to convert a large codebase to TypeScript without creating days or sometimes weeks of work.


The best way to embark on this transition is to make small, incremental changes to your codebase and quickly get those changes into main. You don't want to have some long-running TypeScript branch while other members of your team are making changes upstream; that is setting yourself up for more work and a merge conflicts nightmare. Making focused efforts to push forward basic conversions to TypeScript will save a lot of time. Get your codebase converted to TypeScript before you type everything perfectly right away.

Get the Compiler Running

In this first step, set up the Typescript compiler with the most permissive settings. This is not the time to enable strict mode. In this first phase, disable noImplicitAny and rename all your files from .js to .ts (use this bash script).

{ 
   "compilerOptions":{
      "baseUrl": "src",
      "target":"es5",
      "allowJs": true,
      "skipLibCheck": true,
      "noImplicitAny": false,
      "moduleResolution": "node",
      "module": "esnext",
      "jsx": "preserve",
      "strict": false,
   },
   "include":[ 
      "src/**/*"
   ],
   "exclude":[ 
      "node_modules"
   ],
}

At this stage, only fix errors causing Typescript compiler errors, being careful to avoid functionality changes to the codebase.

Depending on your application, many of the errors you will find at this point involve defining what function parameters are required or optional, typing event onChange handlers, typing React Component props, etc.

Property 'children' is missing in type '{ title: string; items: any[]; secondary: any; small: any; }' but required in type 'Pick<Pick<{ items?: any[]; title?: string; showTitle?: boolean; children: any; minWidth?: string; placement?: string; ignoreBoundary?: boolean; primary: any; secondary?: boolean; small?: boolean; style: any; }, "items" | ... 6 more ... | "small"> & Pick<...> & Pick<...>, "items" | ... 3 more ... | "tertiary">'.ts(2741)

Don't shy away from using the explicit any type at this point. You can add more meaningful types later.

const App = ({ props }: any) => <div>{props.message}</div>;

Disable noImplicitAny

Next, set noImplicitAny to true. Your goal for this step will be to provide more meaning types where you can or add explicit any.

The compiler will no longer infer types in your components / functions.

function fetchData(arg) {
	return fetch(arg)
}
// Error: arg has an implicit 'any' type

function fetchData(arg: string) {
    return fetch(arg)
}

Depending on if you setting your skipLibCheck you may need to import types for your dependencies at this stage as well.

Enable Strict Mode

This last phase will most likely need to happen incrementally. Each team and application will have different needs as far as what how strict you set your compiler.

{ 
   "compilerOptions":{
     "strict": true,
     "noUnusedLocals": true,
     "noUnusedParameters": true,
     "noImplicitReturns": true,
     "forceConsistentCasingInFileNames": true
   },
   
}

You can also extend @typescript-eslint/parser for type specific linting.

In conclusion, the TypeScript compiler is your friend. It has not always felt that way when I am in the middle of converting large amounts of code to TypeScript in the past, but I certainly miss it when I am working in a vanilla JS codebase. Also, the TypeScript documentation is a great resource. You can find helpful guides to assist you in migrating to TypeScript or to learn the language for the first time.