React + TypeScript = ❤️

Why does it have buildings in the background?

TypeScript is quickly becoming a fan favorite of the JavaScript community, adding the age old concept of types to our beloved language. While its just recently became popular it has been around since 2012 (yes, I said 2012 — thats like ancient in frontend dev years) but over the past two years its popularity has risen significantly.

Source: NPM Trends

At the same time, React has also became a fan favorite in frontend community too. Out of the box, React comes pre-configured for Babel and while Facebook has its own type language, Flow, its not as popular as TypeScript and with adoption growing with TypeScript I want to talk about how to take a new or existing Babel project and convert it to TypeScript.

The Create React App Conundrum

Create-React-App also known as CRA is essentially the CLI for React. It will scaffold out a new React project and preconfigure Webpack under the hood for you. Unfortunately it doesn’t support TypeScript so that leaves you with a few options:

Each of the solutions all had their own set of advantages and downsides.

  • Ejecting means you have to manage webpack yourself. Something I’m not wanting to sign back up for after doing it a few projects ago.
  • The Babel TypeScript plugin is experimental and not well tested
  • The rewire only adds TypeScript, it doesn’t setup TSLint, Tests, etc.
  • The alternative CRA is a fork so its not going to be on the latest and greatest.

After looking at all the options, I decided to go with the later of the choices. A fairly popular project called create-react-app-typescript ( creative name huh 😛? ).

Under the covers CRAT is a fork of CRA that adds the TypeScript compiler to Webpack and configures TSLint instead of ESLint. With that said, ESLint does have a experimental plugin for TypeScript but I found TSLint to be a better solution.

Its also worth noting that CRAT also keeps the Babel compiler on board so if you have a existing project its super easy to convert over. I tested it on a existing code base and was able to interopt between TypeScript and Babel without any issues! The downside to that is when you are only using TypeScript you are still installing and running Babel, lesser of the evils I suppose those.

If you are using React App Rewired to extend CRA without ejecting the CRAT project is compatible with that. In your scripts you just specify the scripts version.

{
"scripts": {
"start": "react-app-rewired start --scripts-version react-scripts-ts",
"build": "react-app-rewired build --scripts-version react-scripts-ts",
"test": "react-app-rewired test --scripts-version react-scripts-ts --env=jsdom",
"eject": "react-scripts eject"
}
}

Converting Existing Project

More than likely you already have a existing project using React and Babel, so how do you get from Babel to TypeScript? The folks at Lyft wrote a nice library called react-javascript-to-typescript. The library uses the TypeScript AST parser to crawl the code of your file and convert the files for you. Now it doesn’t do everything but heres a list of things it does do:

  • Proxies PropTypes to React.Component generic type and removes PropTypes
  • Provides state typing for React.Component based on initial state and setState() calls in the component
  • Hoist large interfaces for props and state out of React.Component<P, S> into declared types
  • Convert functional components with PropTypes property to TypeScript and uses propTypes to generate function type declaration

After running this, we found there was some post-conversion things you need to do.

The biggest one was converting import { Component } from 'react'; to the way TypeScript can understand the React import * as React from ‘react’;. This is really frustrating but it sounds like the module interopt isn’t standardized yet accordion to the TypeScript team:

This has to do with the CommonJS/ES module interop, which is not specified in the ECMAScript standard. In the near future, I believe that Babel and TypeScript will be more aligned on this.

There are some work arounds described in this Github issue but when we applied them we ran into other issues. This script will help you convert those automatically.

You can run this conversion on multiple files like so:

find path/to/folder/to/convert -name "*.js" | xargs -n 1 ./ts-converter.sh

Misc Gotchas

Above I mentioned the import issue, there are some other issues that we ran into while doing the conversion.

Confused Compiler
When writing TSX files sometimes TypeScript will think your types are JSX tags. Let’s take a look at the following:

const editTitle = <EditableField>this.refs['editTitle'];

This definition is supposed to read the editTitle off the refs object and cast it to EditableField, however, the TypeScript compiler appears to treat it as a React element expression.

Strict Null Checks
Strict null checks are a TypeScript compiler feature that allows you to be explicit about what can and cannot be assigned a null or undefined. When you enable strictNullChecks and provide defaultProps and later try to use those props, TypeScript can’t resolve that its not null even though it was provided as a default prop. So the following would blow up:

interface TestProps { x?: number}

class Test extends React.Component<TestProps, null> {

static defaultProps = {x: 5};

render() {
const x: number = this.props.x;
return <p>{x}</p>;
}
}

Right after we finished our conversion TypeScript released support for JSX default props in 3.0 which is super awesome, however, React’s .d.ts files do not yet support this functionality so let’s not get too ahead of ourselves.

Many libraries don’t have type definitions
Since the default for React projects is Babel, many of the libraries don’t yet have support for TypeScript type definitions. The TypeScript community is really great at adding these but you gotta remember to add them when you install a new library and if its a lesser known library you might have to write your own.

Importing Images/JSON/etc
In Babel and Webpack its really common to import assets like images, svgs or JSON files. TypeScript is going to bug out on this since it can’t resolve the type for that. I recommend creating a typings.d.ts file in the root of the project and adding the following wildcard module declarations:

declare module '*.png';
declare module '*.jpg';
declare module '*.json';
declare module '*.svg';

Learning from the best

While the number of React projects written in TypeScript isn’t the broadest selection, there are a handful of projects that are good examples you can learn from.

Wrapping Up

I think TypeScript is a great tool to add to your toolbox. It might not be for everyone but it as you scale projects its a savior.

I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!

Like what you read? Give Austin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.