programming

November 15, 2021

React + TypeScript: Know Your Props

I recently switched all of my React projects to TypeScript. Here's why you should consider it, too.

Author: Tristan Brewster

A React app bootstrapped with Create React App (CRA)

I was initially reluctant to give TypeScript (TS) a fair chance.

Well, not entirely. I did spend a few hours going through the official tutorial a few months ago. I don't recall having an inherently negative feeling about it; at the time, though, I didn't have a good enough reason to refactor all of my projects with it.

While working on a secret project over the last few months, I've started to look for more ways to improve my knowledge and skills in React. Spending a few days "checking out" of my daily routine of constant work, I went through a variety of books and tutorials explaining some advanced React patterns and methods.

One consistent factor between them all was, as you can likely guess, TypeScript.

Being consistently reminded about TypeScript, both from these explanations and from more GitHub repositories than I can count, I decided to give it my unbiased attention. It's not like learning a new language, mind you. TS is self-described as the typed superset of JavaScript. It doesn't change much at all. It does, however, add quite a lot of value (if you use it correctly).

This article is primarily about the benefit of using TypeScript with React, but it also doubles as a mini introduction to the language specification itself. For an actual introduction, I'd reccommend typescriptlang.org.

How to use TypeScript effectively

TypeScript will really only be useful to you if you do something very important:

Actually use it, and properly at that. Simply typing everything that gives you an issue with any is essentially like writing vanilla JavaScript.

The power of TypeScript

TypeScript on its own, in a static state, is incredibly useful. Looking through files, finding those type declarations adds an incredible amount of deeper understanding to the data flow within the program. I think, however, that TypeScript is truly a more active subset of JavaScript. That is, its deeper powers are unlocked with your editor of choice.

Consider the following code:

T1

Using VSCodium, as an example, when I add the User type to a new constant and start typing the string for the accountStatus field, you'll see that I am presented with a list of options:

T2

In my opinion, this is a powerful feature that I really appreciate. The same goes for inline hints, on-hover in-code documentation, and so on.


You may be thinking, "okay, that's all well and good, but I know the types of the values within my own project!"

Sure, that may be true. I offer two rebuttals, however:

  • What if you leave this codebase and come back a year later? Would you prefer to re-read some source files, searching through the mess of code to find the specific type?
  • How often do you work with external, NPM repositories? Probably a lot, if you're writing modern JavaScript. If so, wouldn't it be nice if your editor could suggest and autocomplete the types and their values from exported objects from external packages?

Using TypeScript incorrectly

Shifting your codebase over to TypeScript can be a.... time consuming endeavour. More so, when you start getting type errors that you don't know how to solve.

Consider the following React component:

T3

children is a special React prop. It can be a string, array of JSX elements, a normal array, or a React component. Most beginners start by simply ignoring TypeScript's hints altogether and use the any keyword like so:

T4

any is, as the name suggests, a keyword to allow absolutely any type. It's essentially a "cheat" method when used improperly. Of course, it can very well be used correctly, in which case it's more than helpful. Just know that this is not a replacement for // @ts-ignore.

So... what do we actually put here? Do we have to manually use unions to declare all possible types for the children prop for each and every one of our React components like so?

T5

Luckily, React is very aware of TypeScript (and its benefits!). An article by Carl Rippon clearly explains what we actually have to do:

T6

TL;DR: Don't use any as an escape hatch to avoid TypeScript's errors. If you truly cannot figure it out, go ahead and use it but be sure to leave a comment reminding yourself to figure out why it's giving you an error. TypeScript warns you for a reason.


Maps and props

.map() and { props } are the epitome of clean React patterns. TypeScript makes them both a superpower.

Maps

Modern React applications make proper use of the .map() method to describe an array as a series of React components:

T7

One consistent attribute about this very common approach is that most uses of map() pull from some static array of objects. That is, each of them share similar qualities. TypeScript greatly enhances this pattern by allowing you to describe each mapped item as a specific type.

Let's continue using the posts example here. We begin by extracting the base values from each object into a specific type. Then, we declare the posts array as an array of Posts:

T8

TypeScript will then warn us if one object, for instance, returns an integer as the slug instead of a string. In some cases, this may even highlight some issues with your API. Is it returning something you don't expect it to?

There's not much more I can say about maps. It's dead-simple. Simply define a base type for each object and declare your array of data as an array of that base type.

Props

This is, in many ways, the primary purpose of this article. It's in the title, after all!

I've been working in and around React projects for the last 2 or so years. I've written probably thousands of components altogether. In this time, I've had my fair share of preferential changes. I used to prefer using the classes prop to declare custom classes for components; I now prefer mods, however.

These changes in style can happen quickly. Often, I'll change my preference within a codebase. This can be problematic. What if my Button component expects a classes prop but my new RoundButton components uses the cooler (IMO) mods prop? Well, I'll only find out if I either test it or pay attention. ("Why isn't this dark-invert class being added to the Button!?")

TypeScript catches these prop errors quickly, and prevents the need for me to test them.

Consider the following component:

T9

Unless you've spent some time using TypeScript in React projects, you may not be able to locate the potential error here.

Consider this: What if the Section in the homepage doesn't need custom classes? Therefore, mods would be undefined.

So how do we fix this? Two ways. We first declare the mods field in the SectionProps interface as optional with ?. Then, we set a default value for mods within the prop declaration:

T10

That's perfect and easy to do. TypeScript will then warn us if, for instance, some component tries to add a classes property to the Section component:

T11


Typing state: an extension of props

Quickly, I'll provide some examples of how TypeScript can help with React state likewise. Imagine we've created a custom hook, usePromise, which exports a status state which tracks the current status of the Promise. We're refactoring this hook. Components that utilize it expect very specific values to be returned; if we change loading to, for instance, isLoading we may break a lot of things.

Let's take a look:

T12

Without TypeScript, if we were to refactor the usePromise hook and accidentally change the value from loading to isLoading, our Home component wouldn't render properly:

T13

It would never show the loading state. If we were working with a smaller codebase, this may not be an issue. We may notice this right away.

Pay attention to how I described that. "We may notice this." That's too risky for me. We need to notice this. TypeScript helps with that.

TypeScript would warn us, however. We'd know that any component utilizing the loading value from the status state would render abnormally, not showing the Loading component.

Conclusion

Working with TypeScript this last week has already proven to be incredibly useful. I've seen how some of my mapped data was being incorrectly represented—passing invalid props and whatnot. Not to mention, it enforces proper type practice. It would be unexpected for a setCount state to suddenly be set to a string when the default value is a number, for example.

Give it a fair shot. You may enjoy writing JavaScript more.

Thanks for reading.

End of Line.

Infinium

Building and maintaining the future of open-source.

Contact us
Copyright © 2021 Infinium LLC. All rights reserved.

Made with in Salt Lake City.