One of the things I like the most about working with my partners at Nelio, Ruth and Toni, is the possibility of choosing what projects we’ll work on and how we’ll implement them. We also enjoy learning and trying out new technologies whenever we can. So it shouldn’t come as a surprise that every time we start something, we see it as an opportunity to integrate what we’ve learned so far and thus do things better.
It today’s post, I want to talk you about types and, in particular, how a good type definition in TypeScript can guide the development of our plugins, allowing us to create a more robust, reliable, and maintainable software. And, in addition, I’m going to explain it with a real example, Nelio Popups, which Ruth told you about a few days ago. I hope you enjoy it and that, like us, you feel like trying it in your next projects.
An Overview of TypeScript
As we have already mentioned in previous posts, TypeScript is a programming language that extends JavaScript with types. Its aim is simple: strong typing of our JavaScript code will help us to prevent runtime errors (as the compiler will detect them at compile time) and will lead to better, more reliable software.
For instance, imagine we have an object with information about a user: name, password, etc.
const u = {
name: 'David',
password: 'some-nice-password',
};
Next, suppose we have a function that checks if a user’s password is “safe” (i.e. it has 8 or more characters):
function hasSafePassword( user ) {
return 8 <= user.pasword.length;
}
hasSafePassword( u ); // ERROR!
Well, it turns out that in less than 10 lines of code, we have an error that, at first glance, is difficult to catch. We have to run the code to make it obvious:
Uncaught TypeError: user.pasword is undefined
hasSafePassword
Here’s what went wrong: our user doesn’t have a pasword
attribute… because we’ve misspelled it! It’s actually password
with two s
…
Had we implemented this same code using TypeScript and provided a type definition for a User
object as follows:
type User = {
readonly name: string;
readonly password: string;
};
// A
const u: User = {
name: 'David',
password: 'some-nice-password',
};
// B
function hasSafePassword( user: User ) {
return 8 <= user.pasword.length;
}
// C
hasSafePassword( u );
the compiler would have been able to catch the error right off the bat:
Property 'pasword' does not exist on type 'Person'. Did you mean 'password'?
A pretty simple example which, let’s be honest, we both know you and I have faced several times… which can be quickly addressed by a robust type system.
Union Types
One of the coolest things about TypeScript is the fact that any variable or parameter can have more than one type. In the previous example, for instance, we’ve seen that the constant u
has to be exactly of type User
, as well as the user
parameter in the hasSafePassword function. Now, there are cases in which we need a variable or parameter to have different types. A paradigmatic example of this is a Redux reducer.
As we discussed in a previous post, a Redux store allows us to keep track of our app’s state. To update it, we have a reducer
method that takes two arguments (the current state of our application and an action with the necessary information to update it) and produces a new state:
function reducer( state: State, action: Action ): State
Logically, the actions a reducer
receive are all different from each other. For example, if we’re implementing a TODO list, we may have actions like these:
- Create a task: given an ID and the description of a task, add it in the application state.
- Delete a task: Given the ID of a task, remove it from the application state.
- Change the status of a task: given the ID of a task, mark it as completed if it was pending (and viceversa).
In TypeScript:
type NewTask = {
readonly type: 'NEW_TASK';
readonly id: string;
readonly task: string;
}
type RemoveTask = {
readonly type: 'REMOVE_TASK';
readonly id: string;
}
type ToggleTaskStatus = {
readonly type: 'TOGGLE_TASK_STATUS';
readonly id: string;
}
As you can see, the actions of such a small application would either be of type NewTask
, RemoveTask
, or ToggleTaskStatus
. This is what is known as a “type union,” which TypeScript represents using the pipe symbol:
type Action =
| NewTask
| RemoveTask
| ToggleTaskStatus;
Type Guards
When the code is running, an action
in our TODO list example will have one specific type only. If you look closely at their definition, you’ll notice NewTask
, RemoveTask
, and ToggleTaskStatus
all have a type
property (which, by the way, is a standard convention in Redux stores) and an id
property. You’ll also notice that NewTask
is slightly different: it also has a task
property.
Before using an action
, we need to know its exact type. To do so, we use a thing called “type guards.” A type guard is a simple function that validates if a certain variable has one specific type or another. In the case of actions, this check is super simple, because all we need to do to know the specific action type is look at its type
attribute, duh:
function reducer( state: State, action: Action ): State {
switch ( action.type ) {
case 'NEW_TASK':
return {
...state,
[ action.id ]: {
completed: false,
task: action.task,
}
};
...
}
}
Therefore, we can use a switch
block to do one thing or the other depending on the action
we received. When type
is NEW_TASK
, we know for sure that we’ve been given a NewTask
action and, therefore, action
does contain the task
attribute.
This type of union of types is called “discriminated union of types” because, as its name indicates, it is a union of types, all of which share a common attribute (in this case, type
) that allows us to discriminate one type from the other.
But not all type unions have a discriminator. It is perfectly possible that two different types do not have a specific attribute to tell them apart:
type Element = Post | Task;
type Post = {
readonly id: number;
readonly title: string;
}
type Task = {
readonly id: number | string;
readonly task: string;
readonly completed: boolean;
}
So the following question arises: how can we tell if a given Element
is actually a Post
or a Task
? Well, we need to use “type predicates”. A type predicate is basically a predicate function (i.e. a boolean function) that’s responsible of checking if a certain variable is of a certain type or not:
const isPost = ( el: Element ): el is Post =>
undefined !== ( el as Post ).title;
const isTask = ( el: Element ): el is Task =>
undefined !== ( el as Task ).task;
isPost
and isTask
are two different functions that receive an Element
object and tell us if the Element
is a Post
or a Task
respectively. To do this, they both take advantage of the fact that there are attributes that only Post
or Task
have. So, for example, if el
has a title
, we know it’s a Post
.
With these type predicates we can then do things like this:
function stringify( el: Element ): string {
return isPost( el ) ? el.title : el.task;
}
where we use the isPost
type guard to check if the element is a Post
or not. If it is, we return its title
; if not, TypeScript knows el
must be a Task
and, therefore, we can look at its attribute task
. You can play with this example here and see there aren’t any errors.

Nelio Popups
Fantastic plugin! It’s really easy to create popups as you’re already used to the editor, and all the options it has are really well crafted.

Juan Hernando
Now, what?
I hope you found this short introduction to TypeScript interesting. Now that you have a better understanding of some basic type theory, you know everything you need to know to write better software. Next week we’ll see how union types and safe guards will lead to better designs. We’ll also learn the importance of “making impossible states impossible.”
See you next week!
Featured image by Kelly Sikkema on Unsplash.
Leave a Reply