Discriminated unions in Typescript: Use cases

Vasilyev Maksim
PASHA Bank
Published in
3 min readJan 19, 2021

This article is also available in russian.

In the first part of this article, I talked about what discriminated unions (hereinafter DU) are useful for. I advise you to read it if you haven't already. In this article, I will talk about how to use DU in real-world projects.

Asynchronous data

Classic. The vast majority of client applications request data from the server. IAsyncData interface describes this process.

In my experience, this approach saves you from “null ref” errors in react applications (and not only). That’s why:

Let’s say we have a “bad” (no DU) version of an async list of users, called asyncUsers, that is passed to the components via react context. There are two components: Child and Parent. Both take users out of context. Child renders the list of users as a table. Parent, depending on the status, renders either Child or a loading indicator.

This means that Child is rendered only if asyncUsers.status == ‘success’ is true. So does it make sense to recheck the status in Child component? Or we can directly access asyncUsers.data? It would seem that we can… Until we move Child above or next to Parent in components hierarchy, where no one bothered to check asyncUsers.status for Child. This is where we may encounter a classic “null ref” error.

Using the “good” model (with DU), typescript will force us to check asyncUsers.status before accessing asyncUsers.data in each component. Voila, no mistakes! 😉

Validation

I come across the following approach in validation quite often: the validation function/class returns nothing if the data is valid, otherwise it throws an exception with a list of errors inside. For me, this goes against the very essence of exceptions. An exception should be thrown when the situation is exceptional (I apologize for the tautology once again 😁). Invalid data is one of the expected scenarios. The purpose of validation is to draw the line between valid and invalid data.

Instead of throwing an exception or returning nothing, you can always return an object of the following type:

Data transfer objects (DTO)

Oftentimes, front-end DTO can be described in a more accurate way than back-end DTO. The reason is that server-side languages don’t support DU quite often.

Assume that we are working on a document management system. Business asks us to implement the document signature functionality. Also, you need to show the signature date in the interface.

First of all, back-end developers added a new signed status to the document DTO. It remains to add the signature date signedAt. If the back-end is written in Java, then the only way is to make signedAt optional and to add it to other fields. But you don’t have to do the same on the frontend! Because we have DU 😏

Also, if we are dealing with a versioned API, we can describe all versions of the model with one type using DU:

Flow-based API design

Flow-based API design is an alternative way to design service classes and is one of the prime examples of elegant use of DU. I’ll explain to you what it is in a nutshell, but I strongly advise you to read the original article containing real-world examples of use.

Let’s say you have a service with three asynchronous methods: a, b, and c. According to business logic, it’s required to call methods only in alphabetical order: a → b → c. Invocation in another order makes no sense. This is what “classic” and “flow-based” services look like:

The flow-based interface reflects the correct order of calls, while the classic one doesn’t.

Write elegant code! Thanks for your attention 😊

Feel free to reach out to me if you have any questions or would like to connect, either through Twitter, LinkedIn, or just plain old email: vasilyev.maksim93@gmail.com

--

--