Converting React To TypeScript

May 25, 2017 0 Comments

Dev Sandbox

 

 

On any project that will need regular maintenance I tend to prefer TypeScript over JavaScript. Considering that I code most web UI projects with React I end up combining the two - and it works very well. As with most JavaScript libraries these days React TypeScript definition files are available and up to date. You shouldn't fear the case where something you enjoy using is not supported - worst case it is simply writing JavaScript.

JavaScript

Lets take a common use case - A generic react component written as an ES6 class transpiled with Babel:


import React from 'react';
import PropTypes from 'prop-types'; class Input extends React.Component { constructor(props) { super(props); this.state = { value: '', }; this.handleChange= this.handleChange.bind(this); } handleChange(event) { this.setState({ value: event.currentTarget.value }); } render() { return ( <div className="input__container"> <span>{this.props.label}</span> <input value={this.state.value} onChange={this.handleChange} /> </div> ); }
} Input.propTypes = { label: PropTypes.string.isRequired,
}; export default Input;

We have a stateful class that wraps an input field, pretty straightforward. Lets convert this to TypeScript and then go over the differences.

TypeScript

import * as React from 'react'; export interface IProps { label: string;
} export interface IState { value: string;
} class Input extends React.Component<IProps, IState> { constructor(props: IProps) { super(props); this.state = { value: '', }; this.handleChange = this.handleChange.bind(this); } private handleChange(event: React.FormEvent<HTMLInputElement>): void { this.setState({ value: event.currentTarget.value }); } public render(): JSX.Element { return ( <div className="input__container"> <span>{this.props.label}</span> <input value={this.state.value} onChange={this.handleChange} /> </div> ); }
} export default Input;

Not that much longer but certainly more verbose. The structure is very close to JavaScript so it should be readable to anyone who has worked in React before. Lets break this down and see what the differences are.

Imports

In our JavaScript/ES6 class we did import React from 'react', but add an * as for TypeScript. This difference is derived from the ES6 import system. import React from 'react' targets a default export - similar to what is defined in our class at the bottom.

The React library itself uses module.exports instead. Technically, according to specifications, the Babel implementation is incorrect - Babel interprets this import as valid for convenience. In your tsconfig settings there is a similar option, allowSyntheticDefaultImports, which has the same functionality.

I leave the * as here for demonstration as it is the default behavior.

IProps & IState

IProps and IState are interfaces - they define the "shape" of a JavaScript object. By defining these we are defining the "shape" of the component's props and state. This is analogous to and replaces .propTypes - the difference is that the React prop-types library checks at run time - TypeScript does this at compile time (i.e. while you are coding).

Additionally I export these so that they can be imported to help us define our automated test suite.

private & public

Before we dig into handleChange you'll notice we add a private keyword. This tells TypeScript that if this class is imported in another file that this method is not available for use, it is internal. For example I could not do this:

const input = new Input();
input.handleChange(undefined);

This is purely for the developer and maintainability - ultimately TypeScript compiles to JavaScript and that method is on the prototype chain and nothing prevents it from being called.

On render we have public - this means that this method can be called after initializing the class (although in practice you probably wouldn't want to do this). If not specified then public is used as default by the compiler.

handleChange

The function remains the same - it takes in an event and updates the state. The difference is in the types - React typings come with an entire set of Event interfaces. It also returns type void meaning nothing is returned.

What does this give us? In a TypeScript knowledgeable editor like VSCode we get very good code completion:

value

The next benefit is in error checking. Say the customer doesn't want an textbox, they want a dropdown menu. If we change the input to select we will get this in our terminal:

TS2322: Type '(event: FormEvent<HTMLInputElement>) => void' is not assignable to type 'EventHandler<ChangeEvent<HTMLSelectElement>>'.

This is a great reminder that there is other code your input depends on - and you get it as soon as you make the change in code, not after you are testing it in the browser and it doesn't work.

render

We added a return type to render: JSX.Element. If you ever accidentally return something other than JSX from your render method you will get a nasty InvariantViolation error in your browser console. This catches that right away, as you code.

Conclusion

At the end of this conversion we gained several features that help us catch bugs almost immediately and add a layer of maintainability that is simply not possible with JavaScript.

This is not free though - the code is more verbose and slightly longer, plus the cost of actually doing the conversion. If you have a large existing project it is a non-trivial decision you and your team must make to see if it is worth it to convert.

If there is anything I missed or doesn't make sense feel free to contact me, I'm always interested in getting other people's perspective.


Tag cloud