Converting a React component from JavaScript/ES6 to TypeScript

A basic example of how to use TypeScript & React together just as easily as with normal ES6

June 4, 2017

I was reading some programming talk online about the discussions around TypeScript vs JavaScript. The subject of strongly typed vs. weakly typed languages is always a lightning rod for passionate discussion and this thread was no different.

One post that was fairly popular noted that because they were using React they couldn't use TypeScript. That at best they could use Facebook's Flow, another typed variant. However this isn't true; I have been using TypeScript for several months now in most of my React projects, especially the new dashboards I am building at work. Other posters also pointed this out, but I thought I'd write up an example of how to convert a fairly typical React component into its TypeScript counterpart

So we start with our React component in its ES6 variant. A simple form with some input field bound to the component state:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Form extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    event.preventDefault();
    this.setState({ value: event.target.value });
  }
  
  handleSubmit(event) {
    event.preventDefault();
    this.props.fireFormAction(this.state.value);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <h3>{this.props.label}</h3>
        <input value={this.state.value} onChange={this.handleChange} />
      </form>
    );
  }
}

Form.propTypes = {
  label: PropTypes.string.isRequired,
  fireFormAction: PropTypes.func.isRequired,
};

export default Form;

Now for the TypeScript variant:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

interface IProps { label: string; fireFormAction: (value: string) => void };
interface IState { value: string; }

class Form extends Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      value: '',
    };
  }

  private handleChange = (event: React.FormEvent<HTMLInputElement>): void => {
    event.preventDefault();
    this.setState({ value: event.target.value });
  }
  
  private handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
    event.preventDefault();
    this.props.fireFormAction(this.state.value);
  }

  public render(): JSX.Element {
    return (
      <form onSubmit={this.handleSubmit}>
        <h3>{this.props.label}</h3>
        <input value={this.state.value} onChange={this.handleChange} />
      </form>
    );
  }
}

export default Form;

You'll notice that the main changes are in the function declarations and how the properties are defined. In TypeScript we define "interfaces" to set the shape of an object. So the shape of our props is a label and the fireFormAction. We define a second interface to define the value on the state object.

The function declarations are specifically setting types on the input and output variables. This lets us use autocomplete features available in most IDEs to quickly search through a variable for all of its properties and methods. We even define the JSX.Element on the render method to make sure we are always returning JSX from it.

So nothing groundbreaking here, and I think this is a pretty trivial example that doesn't quite do TypeScript justice, but its a decent enough starting point to demonstrate that the difference between the two is really not too severe.