Just enough TypeScript for React Apps

You only need very little

Rajesh Naroth
5 min readDec 10, 2019
Image by OpenClipart-Vectors from Pixabay

Recently I had the opportunity to develop an enterprise app from ground up. I used it as an exercise in finding out how much typing you need to learn to create React apps. It turns out that you can achieve fair type coverage with very little TypeScript.

Typing React Apps

Basic Types

https://www.typescriptlang.org/docs/handbook/basic-types.html

  • boolean, number, string, array, object, tuple

These are fairly easy to remember. “tuples” are arrays that contain specific element types in a specific order.

Enums

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

Enums come in handy especially when you have to define action types in Redux.

export enum Action {
SET_ITEM_LIST,
SET_SELECTED_ROWS,
SET_SELECTED_ITEM
}

any

Any is what I use when I find myself spending more time typing than solving my domain problem. It is a placeholder for me to comeback later and see if there is a better solution. I won’t add some clever piece of enigmatic typing solution. It is not a test. My code is there for the next person who will be tasked with maintaining it. Simplicity rules.

No more classes

With the introduction of Hooks, there is no need to use class components anymore (except for ErrorBoundary). You can discard a big chunk of TypeScript right there. For other OOP constructs you can use functional programming patterns.

PropTypes

Replace PropTypes with Interfaces. TypeScript interfaces are very clean and an excellent replacement for PropTypes.

interface ILabelProps {
name: any[];
}
const MyLabel = ({ name }: ILabelProps) => <p>{name}</p>

Perfect.

Typing functions

When it comes to functional patterns TypeScript is too wordy. Inline typing makes your code unreadable. I like to separate type definition from the function definition. I may move the types to the top of the file or into a separated type file.

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

It is two lines but each line does one thing. The first one defines the type. The other one defines the function.

Because typed function arguments must be inside parentheses, you will also lose the ability to write simple arrow functions like these. :-(

let curriedAdd = a => b => a + b;

Destructured function arguments

For this code:

const SelectPulldown = ({label, items}): void => {
}

TypeScript demands that we do this in the function definition:

const SelectPulldown = ({label, items}: { label: string, items: string[] }): void => {}

I like a two step approach:

interface ISelectPulldown {
label: string;
items: string[];
}
const SelectPulldown = ({ label, items }: ISelectPulldown): void => {
}

If you ask me the syntax noise is getting on my nerves.

Redux

Here is a action/reducer example.

interface IReducerAction {
type: string;
data: any; // yes. "any"!
}
// Actions
enum Action {
RESET = "RESET",
ADD_NUMBER = "ADD_NUMBER"
}
// Action Creators
const addNumber = ({ amount }: { amount: number }) => ({
data: { amount },
type: Action.ADD_NUMBER
});
const reset = () => ({
data: {},
type: Action.RESET
});
// Reducer
export interface IReducerState {
count: number;
lastActivity: string;
}
export const initialState: IReducerState = {
count: 0,
lastActivity: "none"
};
type ReducerType = (state: IReducerState, action: IReducerAction) => IReducerState;
export const reducer: ReducerType = (state = initialState, action) => {
switch (action.type) {
case Action.ADD_NUMBER:
return {
...state,
count: action.data.amount,
lastActivity: `Added ${action.data.amount}`
};
case Action.RESET:
return {
...state,
isBusy: false,
isStale: false,
isError: false,
errorMessage: null
};
default:
return state;
}
};

One thing that will stick out is that my reducer actions data is typed “any”. I could type all the possible action types and then define it something like this:

interface IReducerAction {
type: string;
data: ResetData | AddData
}

To me, it is just too much syntax noise. My unit tested action creators guarantee the integrity of the actions and its payload. TypeScript adds no extra value. These types are internal to the reducer flow.

extends

You can extend a type from another just like the OOP classes. Useful.

interface Dog extends Animal {
bark: () => void();
}

Combiners

const zipcode: string | number;

Using third party libraries with out type definitions.

You’ll need to add a shim to stop TypeScript from throwing the error:

Could not find a declaration file for module 'xxx-yyy'. '...../node_modules/xxx-yyy/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/xxx-yyy` if it exists or add a new declaration (.d.ts) file containing `declare module 'xxx-yyy'

This one is a head scratcher with no clear direction provided to solve. Create a file index.d.ts inside src/@types and add this line in it.

declare module "xxx-yyy";

That’s it. TypeScript is not a swiss army knife to write bug free code. It is not a path for Java engineers to write JavaScript. It is just a tool that provides static typing to help documenting and communicating your code for others to use. Focus on your unit and acceptance tests. When it comes to TypeScript in enterprise React Apps, use just enough. To me a few things stick out.

Syntax Noise

TypeScript will add a tremendous amount of noise especially for function signatures. Recently I tried to teach a new group of fresh engineers on how reducers work. It was just so confusing with the TypeScript noise. Finally we all jumped on to codesandbox.io and knocked out a custom useReducer() function sans TypeScript. So much simpler.

An overzealous TypeScript enthusiast can do some damage to your code readability. Here is an example from the typescript documentation:

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

becomes

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

And then you also will find stuff like this:

function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null

KISS principle and code readability takes a hit with expert clever typing.

Loss of developer momentum

I constantly struggle trying to type simple functional patterns and while writing tests. This pulls me out of solving the domain’s problem and puts me into a “how to type this” google spree. I can blame this on my lack of expertise but I can’t help think how this affects a junior programmer. Learning curve is not a simple thing. To scale a dev team rapidly, you need to keep it small. To my fellow React developers, my advice is, when it comes to TypeScript, use just enough.

--

--

Rajesh Naroth
Rajesh Naroth

Written by Rajesh Naroth

Frontend Architect, San Jose, CA

No responses yet