How I Learned to Stop Worrying and Love TypeScript

For React devs, it is not hard as you think

Rajesh Naroth
7 min readAug 25, 2020
Source: Wikipedia

A few years ago, I started seeking simplicity and minimalism in everything. It reflected in my work too. So, when the TypeScript “hype” started, I simply shut myself out to it because adopting it seemed like a huge undertaking and it would disturb my zen. It would take a year of trying it out in a large enterprise application before it all changes. In hindsight, there are a few things that helped my team and I to adopt TypeScript.

1. Understanding what TypeScript really is

If you go through TypeScript handbook it is unclear where JavaScript ends and TypeScript begins. Thus it is important to know the following.

What is TypeScript?

  • TypeScript adds static typing to normal JavaScript code. static is the keyword here, there is no runtime protection. Typed objects can be updated with wrong data types during runtime, your API can send the wrong data and crash your page. This is also the reason it is quite hard to type dynamic types within functional programming patterns.
  • Sweet features such as destructuring, spread operator, async/await etc are still JavaScript, ES2015 to be specific. TypeScript allows you to adopt the latest JavaScript features such as optional chaining. In my experience, TypeScript has kept up with new features very regularly; much better than babel.

To a newcomer TypeScript looks daunting, it was to me. I still hear voices of resistance from my team when I recommend using TypeScript. In this article, I will try to help React developers on the fence on how to approach TypeScript by trimming its features and using only just what you may need.

There is no shame in using “any” or using // @ts-ignore

Some parts of JavaScript are tough to type, for example, Ramda’s compose can only chain up to 6 functions. External libraries may present crazy typing issues. You can either spend a lot of time trying to find a complex type solution, or you can move on and focus on the business logic. IMO, writing code should be about solving a domain problem and not beating your head against the language/library idiosyncrasies, it is highly inefficient when you have to spend hours googling to know how to type a JavaScript object or function. Just move on with an “any” place holder. Once you have solved your domain’s problem, you can revisit it. Even so, keeping types simple is important. Clever typing must be avoided, make sure that it can be easily reasoned and understood by a newly on-boarded team member.

2. Abandon Classes

I cannot stand writing OOP code any more. After being spoiled by JavaScript where you have clear separation between data and functions, it is really tough to go back and start writing code where everything is a class. It is very tough to decouple transformations and compose behaviors with a class based language like Java. Plus, the inability to unit test private functions also makes it feel inadequate.

The good news: With React, you can write code that is free of classes, except for ErrorBoundaries but, those are far and few. When you adopt functional components, you can throw away a huge chunk of TypeScript.

3. The basics

The best way to adopt TypeScript is to start with some basic typing. The following examples should illustrate them nicely:

Basic Types

const name: string = "Joe";
const count: number = 9;
const isActive: boolean = false;
const colors: string[] = ["red", "blue"];
enum Color { Red, Green, Blue };
let c: Color = Color.Green;

Combining types (Union)

const zipcode: string | number;

Now zipcode can be a string or a number, it is also useful to quickly define a subset of allowed values.

type FlagColors = "red" | "white" | "blue";

Enums

https://www.typescriptlang.org/docs/handbook/enums.html

A group of related constants should be defined within an Enum:

export enum Action {
SET_ITEM_LIST,
SET_SELECTED_ROWS,
SET_SELECTED_ITEM
}

4. Interfaces

Interfaces are a great replacement to React’s proptypes, it lets you self document your components with zero boilerplate. You can combine it with destructuring like so:

interface ISelectPulldown {
label: string;
items: string[];
tooltip?: string; // ? means that the prop is optional
}
const SelectPulldown = ({ label, items }: ISelectPulldown): void => {
// return component here
}
// orconst SelectPulldown: React.FC<ISelectPulldown> = ({ label, items }: ): void => {
//
}

Interfaces also can help you define static data structures, these are objects that has a defined shape. Like so:

interface Position {
x: number;
y: number;
}

Avoid repetition with ‘extends’

You can tuck away common properties using extends, this is useful when you have a family of components that ‘inherits’ a set of basic props.

interface IText {
text: string;
}
interface INavLabel extends IText {
selected?: boolean;
visited?: boolean;
}

According to this example, you can now define may different types of Text components.

extends is very useful when you need to create very extensible reusable components. Here is a sample component that will accept a color property and any other valid property that you pass into a div element.

interface IWarningMessage extends React.HTMLProps<HTMLDivElement> {
level: number;
}
export const WarningMessage: React.FC<IWarningMessage>
= ({level, children, ...props}) =>
<div className={level === 1 ? "error" : "warning"} {...props}>
{children}
</div>;

You can now call the component like so.

<WarningMessage data-testid="msg-1" level="1">
This is an error
</WarningMessage>

See how you can send in a data-testid without having to define that as a PropType?

5. Typing function signatures

When it comes to functions, TypeScript is too wordy. Inline typing makes your code unreadable:

const myAdd: AddFunction: (x: number, y: number) => number = (x, y) => x + y;

Separating type definition from the function definition can add a bit more clarity.

type AddFunction = (x: number, y: number) => number;
const myAdd: AddFunction = (x, y) => x + y;

It is still too wordy for a function that used to look like:

const myAdd = (x, y) => x + y;

But, typing will protect your functions from wrong usage.

6. Other convenient TypeScript features

Once in a while the following features will come in handy.

typeof

In a pinch, typeof comes in handy to create a type from an already initialized object.

const selectedCity = {
id: "62396051",
status: "Active",
name: "East Creola",
type: "City",
alert: false,
population: 6500,
credName: ""
};
type ICity = typeof selectedCity;

For instance, if you use Formik, you can create your types from the initialValues itself.

keyof

What if I want to create a type that is the keys of an object? Consider the example listed below. I have a hardcoded object that is going to drive this type. When I add a new item, I don’t want to retype anything. You can use keyof utility. It comes in handy and can be used in conjunction with typeof.

const metal = {
lead: { atomicNumber: 82 },
mercury: { atomicNumber: 80 }
};
export type TemplateNames = keyof typeof templates;

Omit

The opposite of extends, Omit creates a new type by omitting certain properties from an interface.

interface IAddress { 
name: string;
street: string;
poBox?: string;
city: string;
}
type JustAddress = Omit<IAddress, "name">;

Partial

Partial makes all the properties of an interface optional, this helps if you want to treat the same interface differently based on a situation.

let addr: Partial<IAddress> = {};
/* the object now becomes : {
name?: string;
street?: string;
poBox?: string;
city?: string
} */

ReadOnly

Converts all properties in an interface readonly.

let addr: Readonly<IAddress> = {};
/* the object now becomes : {
name: string;
street: string;
poBox: string;
city: string
} */

ReturnType

Creates a type based on the return type of a function.

7. Generics

Understanding generics require a bit of mental gymnastics. If you are developing enterprise apps mostly composed of tables, charts and forms, chances are, you don’t have to mess with them much. If you are developing reusable libraries, generics will come into play quite a bit. Checkout this example below from my beloved styled components library. Though it looks alien, it is built out of necessity, it does add value for the library consumers.

type StyledComponentProps<
C extends keyof JSX.IntrinsicElements| React.ComponentType<any>,
T extends object,
O extends object,
A extends keyof any
> =
O extends object
? WithOptionalTheme<
Omit<ReactDefaultizedProps<C, React.ComponentPropsWithRef<C>>
& O, A> &
Partial<Pick<React.ComponentPropsWithRef<C> & O, A>>,T> &
WithChildrenIfReactComponentClass<C>
: never;

Generics are types that have placeholders. To explain, we’ll look at an example of a linked list.

interface LinkedNode<T> {
next: LinkedNode<T> | null;
data: <T>;
}
function addNode<T>(parent:LinkedNode<T>, data: T): LinkedNode<T> {
const node: LinkedNode<T> = {
next: null,
data: data
};
parent.next = node;
return node;
};

Now I can create linked list that can store different types of data.

let node: LinkedNode<string> = getNode("New");
let numberNode: LinkedNode<number> = getNode(2);

Summary

The three biggest problems with TypeScript are steep learning curve, hard to type dynamic/functional patterns and syntax noise. However, it adds tremendous value in creating a large application that can be extended and maintained long term. But be mindful that TypeScript comes from the OOP camp that doesn’t play well with React’s functional nature. HOCs and certain dynamic functional constructs are hard to type. Keep it simple, spending a day to “beautifully” type something is not worth it. Don’t work for the tool, let the tool work for you.

What this article attempted to do is to try and ease you into adopting TypeScript in your React application. As daunting as it looks, learning it progressively is not hard, you can do it in small bites as explained above. Every application is different, you will discover more TypeScript features that will help you further than those listed here. As long as you don’t resort to do clever typing that tickles your programmer ribs, your app will be ok.

--

--