KendoUI

TypeScript and React are an increasingly common pair. Learn how to get up and running with TypeScript for your next React project.

TypeScript is more and more becoming a common choice to make when starting a new React project. It’s already being used on some high profile projects, such as MobX, Apollo Client, and even VS Code itself, which has amazing TypeScript support. That makes sense since both TypeScript and VS Code are made by Microsoft! Luckily it’s very easy to use now on a new create-react-app, Gatsby, or Next.js project.

In this article we’ll see how to get up and running with TS on the aforementioned projects, as well as dive in to some of the most common scenarios you’ll run into when using TS for your React project. All three examples can be found here.

TS and create-react-app

With version 2.1.0 and above, create-react-app provides TypeScript integration almost right out of the box. After generating a new app (create-react-app app-name), you’ll need to add a few libraries which will enable TypeScript to work and will also provide the types used by React, ReactDOM, and Jest.

yarn add typescript @types/node @types/react @types/react-dom @types/jest

You can now rename your component files ending in js or jsx to the TypeScript extension tsx. Upon starting your app, the first time it detects a tsx file it will automatically generate you a tsconfig.json file, which is used to configure all aspects of TypeScript.

We’ll cover what this config file is a little further down, so don’t worry about the specifics now. The tsconfig.json file that is generated by create-react-app looks like:

{
  "compilerOptions": {
    "target": "es5",
    "allowJs": true,
    "skipLibCheck": false,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": ["src"]
}

Funny enough, the App.js file, renamed to App.tsx works without requiring a single change. Because we don’t have any user defined variables, functions, or even props that are being received, no more information needs to be provided for TypeScript to work on this component.

TS and Next.js

With your Next.js app already set up, add the @zeit/next-typescript package with the command yarn add @zeit/next-typescript.

After that, we can create a next.config.js file in the root of our project which is primarily responsible for modifying aspects of the build process of Next.js, specifically modifying the webpack configuration. Note that this file can’t have a .ts extension and doesn’t run through babel itself, so you can only use language features found in your node environment.

const withTypeScript = require("@zeit/next-typescript");
module.exports = withTypeScript();

Create a .babelrc file (in root of project):

{
  "presets": ["next/babel", "@zeit/next-typescript/babel"]
}

Create a tsconfig.json file (in root of project):

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "jsx": "preserve",
    "lib": ["dom", "es2017"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "preserveConstEnums": true,
    "removeComments": false,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "target": "esnext"
  }
}

I would recommend then adding yarn add @types/react @types/react-dom @types/next as well so that our app has access to the types provided by those libraries. Now we can rename our index.js page to be index.tsx. We’re now ready to continue app development using TypeScript.

TS and Gatsby

We’ll start by creating a new Gatsby app gatsby new app-name. After that finishes, it’s time to install a plugin which handles TypeScript for you: yarn add gatsby-plugin-typescript

Although it doesn’t seem to be required, let’s create a tsconfig.json. We’ll take it from the Gatsby TypeScript example.

{
  "include": ["./src/**/*"],
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "lib": ["dom", "es2017"],
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "noEmit": true,
    "skipLibCheck": true
  }
}

Now we can rename src/pages/index.js to be index.tsx, and we have TypeScript working on our Gatsby project… or at least we almost do! Because a default Gatsby project comes with a few other components such as Header, Image, and Layout, these need to be converted into .tsx files as well, which leads to a few other issues around how to deal with props in TS, or other external packages which might not come with TS support out of the box.

We’ll quickly cover a few settings in the tsconfig.json file that are especially important and then dive into how we can move beyond the TS setup by actually using and defining types on our React projects.

What is tsconfig.json

We’ve already seen the tsconfig.json file a few times, but what is it? As the name suggests, it allows you to configure TypeScript compiler options. Here are the default TypeScript compiler options which will be used if no tsconfig.json file is provided.

The jsx setting when being used on a React app whose target is the web will have one of two values: You’ll either choose react if this is the final stage of compilation, meaning it will be in charge of converting JSX into JS, or preserve if you want babel to do the conversion of JSX into JS.

strict is typically best set to true (even though its default is false), especially on new projects, to help enforce best TS practices and use.

Most other options are up to you and I typically wouldn’t stray too far from the recommended setup that comes defined by the framework you’re using unless you have a real reason to.

The Basics of TS

If you have never worked with TS before, I would first recommend doing their TypeScript in 5 minutes tutorial. Let’s look at some of the basic types, without diving into too much detail.

let aNumber: number = 5;
let aString: string = "Hello";
let aBool: boolean = true;
// We can say that ages will be an array of `number` values, by adding `[]` to the end of our number type.
let ages: number[] = [1, 2, 3];

You’ll notice that it basically looks like JavaScript, but after the variable name there is : sometype, where sometype is one of the available types provided by TS or, as you’ll see below, created ourselves.

With functions, we’re tasked with providing the types of both the argument(s), and also the type that will be returned from a function.

// receives 2 number arguments, returns a number
let add = (num1: number, num2: number): number => num1 + num2;
let response = add(5, 6);
console.log(response);

The beauty of TypeScript is that often it can figure out the type of a variable on its own. In VS Code if you hover over the response variable it will display let response: number, because it knows the value will be a number based on the declaration of the add function, which returns a number.

In JS it’s common to receive JSON responses or to work with objects that have a certain shape to them. Interfaces are the tool for the job here, allowing us to define what the data looks like:

interface Person {
  name: string;
  age?: number;
}

const register = (person: Person) => {
  console.log(`${person.name} has been registered`);
};

register({ name: "Marian" });
register({ name: "Leigh", age: 76 });

Here we are saying that a Person can have two properties: name, which is a string, and optionally age, which, when present, is a number. The ?: dictates that this property may not be present on a Person. When you hover over the age property you’ll see VS Code tell you that it is (property) Person.age?: number | undefined. Here the number | undefined part lets us know that it is either a number or it will be undefined due to the fact that it may not be present.

React’s Types

React comes with a number of predefined types that represent all of the functions, components, etc. that are declared by React. To have access to these types, we’ll want to add two packages to our project: yarn add @types/react @types/react-dom.

Let’s say we have the JSX:

<div>
  <a href="https://www.google.com">Google</a>
  <p href="https://www.google.com">Google</p>
</div>

It’s a little hard to catch the mistake right off the bat, but the p tag has an href prop that is invalid in HTML. Here’s where TS can help us a ton! In VS Code, the whole href="https://www.google.com" prop is underlined in red as invalid, and when I hover it I see:

[ts] Property 'href' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>'. [2339]

If I hover over href on the a tag, I’ll see (JSX attribute) React.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined. This means that href is an optional attribute on an anchor element (HTMLAnchorElement). Because it’s optional ?:, it can either be a string or undefined.

All of these type definitions come from the @types/react package, which is a massive type declaration file. For the anchor tag example above, its interface looks like the following, which declares a number of optional properties specific to this type of tag:

interface AnchorHTMLAttributes<T> extends HTMLAttributes<T> {
  download?: any;
  href?: string;
  hrefLang?: string;
  media?: string;
  rel?: string;
  target?: string;
  type?: string;
}

Say Goodbye to PropTypes

React’s PropTypes provided a runtime way to declare which props (and their types) would be received by a component. With TypeScript, these aren’t required any more as we can bake that right into our TS code and catch these issues as we’re typing the code rather than executing it.

Props to Functional Components

From the default Gatsby build, we got a Header component that looks like this (I have removed the styles to make it smaller):

import React from "react";
import { Link } from "gatsby";

const Header = ({ siteTitle }) => (
  <div>
    <h1>
      <Link to="/">{siteTitle}</Link>
    </h1>
  </div>
);

export default Header;

We can see that it receives a siteTitle, which looks to be a required string. Using TS we can declare using an interface what props it receives. Let’s also make it a bit fancier by adding functionality for it to display a subTitle if provided.

interface Props {
  siteTitle: string
  subTitle?: string
}

const Header = ({ siteTitle, subTitle }: Props) => (
  <div>
    <h1>
      <Link to="/">{siteTitle}</Link>
    </h1>
    {subTitle && <h2>{subTitle}</h2>}
  </div>
)

We’ve declared a Props interface that states we will receive a siteTitle as a string, and optionally receive a subTitle, which, when defined, will be a string. We can then in our component know to check for it with {subTitle && <h2>{subTitle}</h2>}, based on the fact that it won’t always be there.

Props to Class Components

Let’s look at the same example above but with a class-based component. The main difference here is that we tell the component which props it will be receiving at the end of the class declaration: React.Component<Props>.

interface Props {
  siteTitle: string
  subTitle?: string
}

export default class Header extends React.Component<Props> {
  render() {
    const { siteTitle, subTitle } = this.props

    return (
      <div>
        <h1>
          <Link to="/">{siteTitle}</Link>
        </h1>
        {subTitle && <h2>{subTitle}</h2>}
      </div>
    )
  }
}

We have two more things left to do to fix up our default Gatsby install. The first is that, if you look at the Layout component, you’ll see an error on this line: import Helmet from 'react-helmet'. Thankfully it is easy to fix, because react-helmet provides type declarations by adding yarn add @types/react-helmet to our package. One down, one more to go!

The last issue is what to make of the line const Layout = ({ children }) =>. What type will children be? Children, if you aren’t fully sure, are when you have a React component that receives “child” component(s) to render inside itself. For example:

<div>
  <p>Beautiful paragraph</p>
</div>

Here we have the <p> component being passed as a child to the <div> component. OK, back to typing! The type of a child in React is ReactNode, which you can import from the react project.

// Import ReactNode
import React, { ReactNode } from "react";
// ... other packages

// Define Props interface
interface Props {
  children: ReactNode;
}

// Provide our Layout functional component the typing it needs (Props)
const Layout = ({ children }: Props) => <div>{children}</div>;

export default Layout;

As a bonus, you can now remove the PropTypes code which comes with Gatsby by default, as we’re now doing our own type checking by way of using TypeScript.

Events and Types

Now let’s take a look at some specific types involved in Forms, Refs, and Events. The Component below declares a form which has an onSubmit event that should alert the name entered into the input field, accessed using the nameRef as declared at the top of the Component. I’ll add comments inline to explain what is going on, as that was a bit of a mouthful!

import React from "react";

export default class NameForm extends React.Component {
  // Declare a new Ref which will be a RefObject of type HTMLInputElement
  nameRef: React.RefObject<HTMLInputElement> = React.createRef();

  // The onSubmit event provides us with an event argument
  // The event will be a FormEvent of type HTMLFormElement
  handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    // this.nameRef begins as null (until it is assigned as a ref to the input)
    // Because current begins as null, the type looks like `HTMLInputElement | null`
    // We must specifically check to ensure that this.nameRef has a current property
    if (this.nameRef.current) {
      alert(this.nameRef.current.value);
    }
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" ref={this.nameRef} />
        <button>Submit</button>
      </form>
    );
  }
}

Conclusion

In this article we explored the world of TypeScript in React. We saw how three of the major frameworks (or starter files) in create-react-app, Gatsby, and Next.js all provide an easy way to use TypeScript within each project. We then took a quick look at tsconfig.json and explored some of the basics of TypeScript. Finally, we looked at some real-world examples of how to replace PropTypes with TypeScript’s type system, and how to handle a typical scenario with Refs and a Form Event.

Personally, I have found TypeScript to both be easy to get started with, but at the same time incredibly frustrating when you run into some strange error that isn’t obvious how to solve. That said, don’t give up! TypeScript provides you with further confidence that your code is valid and working as expected.

For More on Building Apps with React: 

Check out our All Things React page that has a great collection of info and pointers to React information – with hot topics and up-to-date info ranging from getting started to creating a compelling UI.

leigh-halliday
About the Author

Leigh Halliday

Leigh Halliday is a full-stack developer specializing in React and Ruby on Rails. He works for FlipGive, writes on his blog, and regularly posts coding tutorials on YouTube.

Related Posts

Comments

Comments are disabled in preview mode.