React.js: Switch from PropTypes to TypeScript

React.js: Switch from PropTypes to TypeScript

ยท

10 min read

Featured on Hashnode

If you are a React.js developer and does not use TypeScript, this article is for you!

๐Ÿ“ฑ Props are a communication channel

Components are the core feature of the React library. It allows us a real separation of concerns. For building a complete UI, we need several of them, so we have to make them communicate.

The easy way to pass information from one component to another is props:

import React from "react";
import { Picture } from "./Picture";

export const Profile = ({ user }) => (
  <div>
    <Picture source={user.photo} altText={user.name} type="xlarge" />
    <h1>{user.name}</h1>
    <p>{user.bio}</p>
  </div>
);

It's a common pattern: a component get data from props, use others components and give them props. Here, the Profile component is using the Picture component. We use props to give information from Profile to Picture. It's a communication channel, and developers should treat it as such.

So what's a great communication channel? It's not so much the ability to communicate, but rather the UX features around it.

I use Signal to talk to some friends. Of course I can send and receive messages, but what features do we have around this? I have read receipt to know if my message has been read, cool. I don't have the possibility to edit my message, not cool. What about React props? You can provide a value to a component, OK, but what developer experience do we have around this? Type check!

Types can improve DX, as it can warn us when something seems wrong. The standard way to add type check to our props is to use the prop-types library.

import PropTypes from "prop-types";
import React from "react";

const getSize = (type) => {
  switch (type) {
    case "large":
      return { width: 300, height: 200 };

    case "medium":
      return { width: 150, height: 100 };

    case "small":
      return { width: 90, height: 60 };
  }
};

export const Picture = ({ altText = "", source, type = "small" }) => {
  const { width, height } = getSize(type);
  return <img alt={altText} src={source} width={width} height={height} />;
};

Picture.propTypes = {
  alt: PropTypes.string,
  source: PropTypes.string.isRequired,
  type: PropTypes.oneOf(["large", "medium", "small"]).isRequired,
};

Sweet, we can communicate more effectively, as we can specify if a prop is required or not, if it's a string or another type, or even give a strict list of possible values! In our case, here is what the browser tells us when we want to display our Profile component:

error.png

Oh, good catch, thanks PropTypes! Imagine now that we can take this idea of checking types further, and check all our code? ๐Ÿ˜

๐Ÿท Here comes TypeScript.

Before React v15.5, the prop-types library was included in React. The React team made the choice, true to their philosophy of having an unopiniated library, to extract it from the core to allow developers to use other typing systems, such as Flow or TypeScript. Let's try the latter.

One of the barriers to entry for TypeScript is that you have to install and configure the tool before you can use it. Not as simple as an yarn add prop-types, for sure. The best way to learn TypeScript in my opinion is to skip this step entirely. Just create a new React app with CRA, and start hacking!

yarn create react-app my-project --template typescript

Done. Now, let's switch from PropTypes to TypeScript in our Picture Component.

import React, { FunctionComponent } from "react";

// We can create an TypeScript interface
// to describe our components props
interface PictureProps {
  altText?: string;
  source: string;
  type: Size;
}

// We can use all native type such as string, number, boolean
// But we can also create our own type
type Size = "large" | "medium" | "small";

const getSize = (type: Size) => {
  switch (type) {
    case "large":
      return { width: 300, height: 200 };

    case "medium":
      return { width: 150, height: 100 };

    case "small":
      return { width: 90, height: 60 };
  }
};

export const Picture: FunctionComponent<PictureProps> = ({
  altText = "",
  source,
  type = "small",
}) => {
  const { width, height } = getSize(type);
  return <img alt={altText} src={source} width={width} height={height} />;
};

The magic of TypeScript is that it doesn't just strictly apply the types we provide. It also analyzes our code to deduce information.

By analyzing the getSize() function, it can deduce that its return type will be an object of the form { width: number; height: number }. Not bad, eh?

TypeScript also comes with a little extra: it can do that while you are coding, right there into the code editor. You don't have to wait for execution in the browser to get hints, as it was the case with prop-types.

Here's what is displaying in my VS Code:

getSize.png

While we're at it, let's switch our Profile component to TypeScript:

import React, { FunctionComponent } from "react";
import { Picture } from "./Picture";

interface User {
  name: string;
  bio: string;
  photo: string;
}

interface ProfileProps {
  user: User;
}

export const Profile: FunctionComponent<ProfileProps> = ({ user }) => (
  <div>
    <Picture source={user.photo} altText={user.name} type="xlarge" />
    <h1>{user.name}</h1>
    <p>{user.bio}</p>
  </div>
);

Here also, no need to wait for the runtime to be warned that there is a problem with our code. Type checking is done directly at compilation time. That is to say instantaneously when we use create-react-app, since we have ESLint which warns us of errors found by TypeScript directly in our code editor:

type-error.png

Right, "xlarge" is not in the list of values defined in our Size type that we have created. Thanks TypeScript!

Oh, while you are at it dear TypeScript, can you tell us the possible values please?

possible-values.png

Cool, don't you think? ๐Ÿ˜Ž

And what's really cool is that, unlike PropTypes, TypeScript is not limited to component props. We can have warnings on all our JavaScript code, including what is not related to our React components. Even including what is not related to front-end entirely, as TypeScript works like a charm with NodeJS.

Our getSize() function, used by our <Picture> component is just a plain JavaScript function. But since we have typed its parameter, we can also take advantage of the features of TypeScript.

For example, if I want to add a case in the switch statement:

getSizeError.png

Now, we can be confident in our code. If there is an inconsistency somewhere, TypeScript will be there to yell at us, pointing out the mistake that was right under our nose but that we had still missed.

If you want to dive deeper, you might want to check these:

๐Ÿค” Final thoughts

I'm not going to lie, the journey with TypeScript is not an easy ride and the learning curve can be difficult sometimes. But I think it's definitely worth it.

Microsoft has done a great job by proposing an open-source tool, reactive to JavaScript evolutions. The JS ecosystem has adopted this new quasi-standard, and many libraries are compatible with TypeScript, to allow us to type our code, as we did in our examples with React's FunctionComponent.

A few years ago, getting into TypeScript was a gamble. In 2021, it is rather an obvious bet. Use it today!