Produce more declarative TypeScript code with pattern matching

Adrien HARNAY
Brigad Engineering
Published in
4 min readSep 30, 2022

--

wall of colored tiles
Photo by S O C I A L . C U T on Unsplash

If you are not yet familiar with how type inference works in TypeScript, I would suggest you start with my previous article on the topic.

TypeScript’s static type checking is really powerful, and language features like type inference and type narrowing really make the language a joy to work with. But maybe you found yourself having to handle all of the possible types a variable could be, or wanting to narrow it to just a specific type, and you had to write a lot of code — often very imperative code.

Now there is nothing wrong with imperative code, but when working with some data structures or languages (e.g. JSX / React) it can be better to go declarative. This is where pattern matching shines!

What is pattern matching?

From the ts-pattern documentation:

Pattern Matching is a code-branching technique coming from functional programming languages, which lets you scrutinize the structure of values in a declarative way. It has proven itself to be less verbose and more powerful than imperative alternatives (if/else/switch statements), especially when branching on complex data structures or on several values.
Pattern Matching is implemented in Haskell, Rust, Swift, Elixir and many other languages.

What problem does pattern matching solve?

Let’s see a simple example:

TypeScript playground link

This code has a few issues:

  • Adding a new type to the union type of networkStatus will be handled like the 'failure' type. This code is not as “future-proof” as it could be.
  • This code has to be declared inside a function to be called inside JSX. You cannot use if statements inside JSX, only ternaries.
  • With a string as a data structure, it is pretty straightforward; but when you have to deal with more complex data structures or when you need to nest branches, it can become cumbersome.

How can I use pattern matching in TypeScript?

You may have guessed it, ts-pattern is an excellent library to introduce pattern matching in TypeScript. Let’s rewrite the previous example:

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbziAhjAxgCzgXzgMyghDgHIYBnAWjDRgFMoA7UgbgCh30ImL4BzejAByQgO7QA1gFl6FCikFwAvHAAUTcVIDKMNAFcKALjLAmwGMBQAbUnAA+ZaxBQATM-zuPSFfenRyFF5k+CjA1vpQ9KQAlCoAfIjscHBRMJFMyGhYGlpQkroGFDHJKXAAdGIWmGqkZhZWtgA06nHKiaQAKpj0cK5oKHBV1tZwzm5wFJjQMNYAnuWxpSmV1bXj7kyeLWptHd29-XpwwBRjLpv8iyVlFVUwNT5+AfKkO3tkB30DJ2dRbgslrdVg9aqFwpFou8Ep8elEhigzoMwEQAEbWegkNJQYD0ABuHjgD0OA2uywq9AAHpgUIZLHj6LsODggA
TypeScript playground link

We just solved all of the problems we had with the previous version of the code.

Calling .exhaustive() ensures we covered all of the possible types for networkStatus, and if we add a 'timeout' type to the union we get a comprehensive error:

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbziAhjAxgCzgXzgMyghDgHIYBnAWjDRgFMoA7UgbgCh30ImL4BzejAByQgO7QA1gFl6FCikFwAvHAAUTcVIDKMNAFcKALjLAmwGMBQAbUnAA+ZaxBQATM-zuPSFfenRyFF5k+CjA1vpQ9MHkoPQQ+jCkAJQqAHyI7HBwUTCRTMhoWBpaUJK6BhTJWdlwAHRiFphqpGYWVrYANOqpyhmkACqY9HCuaChwjdbWcM5ucBSY0DDWAJ51KTXZDU0tc+5Mnt1qvf1DI2N6cMAUsy4H-BvVtfWNMM0+fgHypMenZOdRuNrrcom51psXjt3i1QuFItE-ukAcMopMULcJmAiAAjaz0Ei5KDAegANw8cHeF3GTy2cE4DMZnCh9AAHpgUIZLKT6CcODggA
TypeScript playground link

It is also possible to call .run() instead of .exhaustive() to not check for exhaustiveness, or to call .otherwise(() => 'Something else happened') to keep the behaviour we had with the last return statement of our first example and have a fall-through case.

As the code doesn’t rely on if statements, if can safely be called in JSX:

TypeScript playground link

Also, ts-pattern works well with all data structures and I encourage you to check their documentation for advanced examples (e.g. the isMatching function).

Can ts-pattern only match literal values?

It can do much more than that! Let’s see a few other examples so you can see what is possible.

Type narrowing in branches:

TypeScript playground link

Branching on primitive types:

TypeScript playground link

Branching on values with .when():

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbziAhjAxgCzgXzgMyghDgCIYBnAWjDRgFMoA7UgbgCh30ImL4BzejAByQgO7QA1gFl6FCikFwAvHAAUTcVIDKMNAFcKALjhN9IAEaMAlCoB8idnDhQh+5sjRYNWqJN0GFNZOznAAdGKY9Ewa5vamcQA8cADMAAxpADTqtsoOAOQAKlFw6CgANuVwYigUcBT66OhyFPj65WH5waHhkdGxJHkJg8qqAKwZ2Wq5BdrEQpjATPwu9BXlAJ5wFigAJnCYKGBg0fS7nd2hYRAwUVBiwBT0atPxRSVllQQowOVnFxwcBwgA
TypeScript playground link

There are lots of other features, check out the documentation!

Conclusion

ts-pattern is a nice addition to our tool kit and we use it extensively at Brigad to produce more readable, concise and declarative code!

--

--