TypeScript and React duking it out over PropTypes (2018)

Mutually Exclusive React PropTypes with TypeScript

Sometimes when making a React component, you define prop types that allow users to pass one of N types of props. For instance, we might have some form of input component that could either be a standard <input/> or a larger <textarea/>. These two HTML elements have certain properties that the other does not.

How I wish it worked

TypeScript can pleasantly surprise you with how smart it is, then make you question its stupidity minutes later (at least as of 2.6.2.) My first attempt at exclusive prop types was succinct, and seemed like it would make sense for TypeScript to follow along with; I’d define a set of base props, and extend that with two sets of exclusive props. From there I could just check if the properties on those types were truthy, and then it would know which type it was.

Unfortunately this doesn’t work. TypeScript won’t allow you to check this.props.type because TextareaProps doesn’t have that property. We could get around this by asserting the type in the conditional, but that wouldn’t handle the else case, so we’d have to assert there too. In any case, assertions are usually a sign that something’s not quite right in your approach.

How to make it work

Method 1 - Shared Prop

If you don’t mind having someone pass another prop, a boolean prop shared between the two interfaces is a great way to differentiate between them, and have TypeScript follow along. You can also provide a default by making the desired default props interface’s shared prop optional with a ? :

If you have more than 2 exclusive prop sets, you can make the shared property the value of an enum. Setting a default still works the same as a boolean prop, just make it optional.

Method 2 - Type Guards

If you want to avoid passing in an extra prop, you can do so with a user defined type guard function (More explanation in this handy guide.) TypeScript allows you to type the return of a function to be whether or not something is of a certain type, by making it arg is MyType. So we can just make a function that checks for one of the properties of your type like so:

We still have to assert props as InputProps in the type guard function, because we can’t access the type property on TextareaProps objects since it’s not defined. We can further clean up our type guard function by adding type: undefined; as a property on the TextareaProps interface, so that TypeScript knows we intend to check against it:

A warning about type guard functions though, TypeScript doesn’t keep track of the type determination if you assign it to a variable, so the function has to be invoked in the conditional. If you assign it to a variable, it thinks it’s just any old boolean:

Final thoughts

Switching to TypeScript has been a huge boon to our team’s productivity, but sometimes it comes to a grinding halt because of these unintuitive work-arounds. But the language is always improving, and I look forward to seeing checks like these becoming more intuitive.

If you want to work with a great team of folks who have been putting together an excellent TypeScript codebase, check out the MyCrypto project on GitHub. Big thanks to Henry Nguyen for working this out with me.

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store