It’s time to continue with (and hopefully finish) our TypeScript tutorial. If you have missed the previous posts we’ve written about TypeScript, here they are: our initial introduction to TypeScript, and the first part of this tutorial where I explain the JavaScript example we are working with and the steps we took to partially improve it.
Today we are going to finish our example by completing everything that’s still missing. Specifically, we will first see how to create types that are partial versions of other existing types. We will then see how to correctly type the actions of a Redux store using type unions, and we’ll discuss the advantages type unions offer. And, finally, I will show you how to create a polymorphic function whose return type depends on its arguments.
A Brief Review of What We’ve Done So Far…
In the first part of the tutorial we used (part of) a Redux store that we took from Nelio Content as our working example. It all started as plain JavaScript code which had to be improved by adding concrete types that make it more robust and intelligible. Thus, for example, we defined the following types:
type PostId = number;
type Day = string;
type Post = {
id: PostId;
title: string;
author: string;
day: Day;
status: string;
isSticky: boolean;
};
type State = {
posts: Dictionary<PostId, Post>;
days: Dictionary<Day, PostId[]>;
};
which helped us to understand, at a glance, the type of information our store works with. In this particular instance, for example, we can see that the state of our application stores two things: a list of posts
(which we have indexed through their PostId
) and a structure called days
that, given a certain day, returns a list of post identifiers. We can also see the attributes (and their specific types) we’ll find in a Post
object.
Once these types were defined, we edited all the functions of our example to use them. This simple task transformed JavaScript’s opaque function signatures:
// Selectors
function getPost( state, id ) { ... }
function getPostsInDay( state, day ) { ... }
// Actions
function receiveNewPost( post ) { ... }
function updatePost( postId, attributes ) { ... }
// Reducer
function reducer( state, action ) { ... }
to self-explanatory TypeScript function signatures:
// Selectors
function getPost( state: State, id: PostId ): Post | undefined { ... }
function getPostsInDay( state: State, day: Day ): PostId[] { ... }
// Actions
function receiveNewPost( post: Post ): any { ... }
function updatePost( postId: PostId, attributes: any ): any { ... }
// Reducer
function reducer( state: State, action: any ): State { ... }
The getPostsInDay
function is a very good example of how much TypeScript will improve the quality of your code. If you look at the JavaScript counterpart, you really don’t know what that function is going to return. Sure, its name might hint the result type (is it a list of posts, maybe?), but you must look at the source code of the function (and probably the actions and reducers too) to be sure (it’s actually a list of post IDs). One can improve this situation by naming stuff better (getIdsOfPostsInDay
, for example), but there’s nothing like concrete types to clean any doubt: PostId[]
.
So, now that you’re up to speed with the current state of affairs, it’s time to move on and fix everything we skipped last week. Specifically, we know we need to type the attributes attributes
of the updatePost
function and we need to define the types our actions will have (note that in reducer
, the action
attribute right now is of type any
).
How to Type an Object whose Attributes are a Subset of Another Object’s
Let’s warm up by starting with something simple. The updatePost
function generates an action that signals our intent of updating certain attributes of a given post ID. Here’s how it looks like:
function updatePost( postId: PostId, attributes: any ): any {
return {
type: 'UPDATE_POST',
postId,
attributes,
};
}
and here’s how the action is used by the reducer to update the post in our store:
function reducer( state: State, action: any ): State {
// ...
switch ( action.type ) {
// ...
case 'UPDATE_POST':
if ( ! state.posts[ action.postId ] ) {
return state;
}
const post = {
...state.posts[ action.postId ],
...action.attributes,
};
return { ... };
}
// ...
}
As you can see, the reducer searches the post in the store and, if it’s there, it updates its attributes by overwriting them using those included in the action.
But what exactly are an action’s attributes
? Well, they’re clearly something that looks similar to a Post
, as they’re supposed to overwrite the attributes we may find in a post:
type UpdatePostAction = {
type: 'UPDATE_POST';
postId: number;
attributes: Post;
};
but if we try to use this we will see that it does not work :
const post: Post = {
id: 1,
title: 'Title',
author: 'Ruth',
day: '2020-10-01',
status: 'draft',
isSticky: false,
};
const action: UpdatePostAction = {
type: 'UPDATE_POST',
postId: 1,
attributes: {
author: 'Toni',
},
};
because we don’t want attributes
to be a Post
itself; we want it to be a subset of Post
attributes (i.e. we want to specify only those attributes of a Post
object that we’ll be overwriting).
To solve this problem, just use the Partial
utility type :
type UpdatePostAction = {
type: 'UPDATE_POST';
postId: number;
attributes: Partial<Post>;
};
And that’s it! Or is it?

Nelio Content
I’m so happy about Nelio Content that I will sound like a payed advocate… but here’s why you’ll love it: it works as promised, its auto-scheduling feature is top-notch, Nelio’s value for money is unmatched, and the support team feels like your own.

Panozk
Filtering Attributes Explicitly
The previous snippet is still faulty, as it’s possible to get some runtime errors that TypeScript’s compiler is not checking. Here’s why: the action that signals a post update thas two arguments, a post ID and the set of attributes we want to update. Once we have the action ready, the reducer is in charge of overwriting the existing post with the new values:
const post = {
...state.posts[ action.postId ],
...action.attributes,
};
And that’s precisely the faulty part in our code; it’s possible that the action’s postId
attribute has a pots ID x and the id
attribute in attributes
has a different post ID y:
const action: UpdatePostAction = {
type: 'UPDATE_POST',
postId: 1,
attributes: {
id: 2,
author: 'Toni',
},
};
This is obviously a valid action, and so TypeScript doesn’t trigger any errors, but we know it shouldn’t be. The id
attribute in attributes
(if present) and the postId
attribute should have the same value, or else we have an incoherent action. Our action type is imprecise because it lets us define a situation that should be impossible… so how can we fix this? Quite easily: just change this type so that this scenario that should be impossible becomes actually impossible.
The first solution I thought of is the following: remove the postId
attribute from the action and add the ID in the attributes
attribute:
type UpdatePostAction = {
type: 'UPDATE_POST';
attributes: Partial<Post>;
};
function updatePost( postId: PostId, attributes: Partial<Post> ): UpdatePostAction {
return {
type: 'UPDATE_POST',
attributes: {
...attributes,
id: postId,
},
};
}
Then, update your reducer so that it uses action.attributes.id
instead of action.postId
to find and overwrite the existing post.
Unfortunately, this solution is not ideal, because attributes
is a “partial post,” remember? This means that, in theory, the id
attribute may or may not be in the attributes
object. Sure, we know it will be there, because we’re the ones generating the action… but our types are still imprecise. If in the future someone modifies the updatePost
function and doesn’t make sure that attributes
includes the postId
, the resulting action would be valid according to TypeScript but our code would not work:
const workingAction: UpdatePostAction = {
type: 'UPDATE_POST',
attributes: {
id: 1,
author: 'Toni',
},
};
const failingAction: UpdatePostAction = {
type: 'UPDATE_POST',
attributes: {
author: 'Toni',
},
};
So, if we want TypeScript to protect us, we must be as precise as possible when specifying types and make sure they make impossible states impossible. Considering all this, we only have two options available:
- If we have a
postId
attribute in action (as we did in the beginning), then theattributes
object must not contain anid
attribute. - If, on the other hand, the action doesn’t have a
postId
attribute, thenattributes
must contain anid
attribute.
The first solution can be easily specified using another utility type, Omit
, which allows us to create a new type by removing attributes from an existing type:
type UpdatePostAction = {
type: 'UPDATE_POST';
postId: PostId,
attributes: Partial< Omit<Post, 'id'> >;
};
which works as expected :
const workingAction: UpdatePostAction = {
type: 'UPDATE_POST',
postId: 1,
attributes: {
author: 'Toni',
},
};
const failingAction: UpdatePostAction = {
type: 'UPDATE_POST',
postId: 1,
attributes: {
id: 1,
author: 'Toni',
},
};
For the second option, we have to explicitly add the id
attribute on top of the Partial<Post>
type we defined:
type UpdatePostAction = {
type: 'UPDATE_POST';
attributes: Partial<Post> & { id: PostId };
};
which, again, gives us the expected result :
const workingAction: UpdatePostAction = {
type: 'UPDATE_POST',
attributes: {
id: 1,
author: 'Toni',
},
};
const failingAction: UpdatePostAction = {
type: 'UPDATE_POST',
attributes: {
author: 'Toni',
},
};
Union Types
In the previous section, we’ve already seen how to type one of the two actions our store has. Let’s do the same with the second action. Knowing that receiveNewPost
looks like this:
function receiveNewPost( post: Post ): any {
return {
type: 'RECEIVE_NEW_POST',
post,
};
}
its type can be defined as follows:
type ReceiveNewPostAction = {
type: 'RECEIVE_NEW_POST';
post: Post;
};
Easy, right?
Now let’s take a look at our reducer: it takes a state
and an action
(whose type we don’t know yet) and produces a new State
:
function reducer( state: State, action: any ): State { ... }
Our store has two different types of actions: UpdatePostAction
and ReceiveNewPostAction
. So what’s the type of the action
argument? One or the other, right? When a variable can accept more than one type A, B, C, and so on, its type is a union of types. That is, its type can either be A or B or C, and so on. A union types is a type whose values can be of any of the types specified in that union.
Here’s how our Action
type can be defined as a union type:
type Action = UpdatePostAction | ReceiveNewPostAction;
The previous snippet is simply stating that an Action
can be either an instance of the UpdatePostAction
type or an instance of the ReceiveNewPostAction
type.
If we now use Action
in our reducer:
function reducer( state: State, action: Action ): State { ... }
we can see how this new version of our code based, which is well-typed, works smoothly.
How Union Types Eliminate Default Cases
“Wait a second,” you might say, “the previous link isn’t working smoothly, the compiler is triggering an error!” Indeed, according to TypeScript, our reducer contains unreachable code:
function reducer( state: State, action: Action ): State {
// ...
switch ( action.type ) {
case 'RECEIVE_NEW_POST': // ...
case 'UPDATE_POST': // ...
}
return state; //Error! Unreachable code
}
Wait, what? Let me explain what’s going on here…
The Action
union type we created is actually a discriminated union type. A discriminated union type is a union type in which all of its types share a common attribute whose value can be used to discriminate one type from the other.
In our case, the two Action
types have a type
attribute whose values are RECEIVE_NEW_POST
for ReceiveNewPostAction
and UPDATE_POST
for UpdatePostAction
. Since we know that an Action
is, necessarily, an instance of either one action or the other, the two branches of our switch
cover all the possibilities: either action.type
is RECEIVE_NEW_POST
or it is UPDATE_POST
. Therefore, the final return
is redundant and will be unreachable.
Suppose, then, that we remove that return
to fix this error. Did we gain anything, beyond removing unnecessary code? The answer is yes. If we now add a new type of action in our code :
type Action =
| UpdatePostAction
| ReceiveNewPostAction
| NewFeatureAction;
type NewFeatureAction = {
type: 'NEW_FEATURE';
// ...
};
suddenly the switch
statement in our reducer will no longer covers all possible scenarios:
function reducer( state: State, action: Action ): State {
// ...
switch ( action.type ) {
case 'RECEIVE_NEW_POST': // ...
case 'UPDATE_POST': // ...
// case NEW_FEATURE is missing...
}
// return undefined is now implicit
}
This means the reducer might implicitly return an undefined
value if we invoke it using an action of type NEW_FEATURE
, and that’s something that doesn’t match the function’s signature. Because of this mismatch, TypeScript complains and lets us know we’re missing a new branch to deal with this new action type.
Polymorphic Functions with Variable Return Types
If you’ve made it this far, congratulations: you’ve learned everything you need to do to improve the source code of your JavaScript applications using TypeScript. And, as a reward, I am going to share with you a “problem” that I came across a few days ago and its solution. Why? Because TypeScript is a complex and fascinating world and I want to show you up to which extend this is true.
At the beginning of this whole adventure, we’ve seen one of the selectors we have is getPostsInDay
and how its return type is a list of post IDs:
function getPostsInDay( state: State, day: Day ): PostId[] {
return state.days[ day ] ?? [];
}
even though the name suggests it might return a list of posts. Why did I use such a misleading name, you’re wondering? Well, imagine the following scenario: suppose you want this function to be able to either return a list of post IDs or return a list of actual posts, depending on thevalue of one of its arguments. Something like this:
const ids: PostId[] = getPostsInDay( '2020-10-01', 'id' );
const posts: Post[] = getPostsInDay( '2020-10-01', 'all' );
Can we do that in TypeScript? Of course we do! Why else would I bring this up otherwise? All we have to do is define a polymorphic function whose result depends on the input parameters.
So, the idea is we want two different versions of the same function. One should return a list of PostId
s if one of the attributes is the string
id. The other should return a list of Post
s if that same attribute is the string
all.
Let’s create them both:
function getPostsInDay( state: State, day: Day, mode: 'id' ): PostId[] {
// ...
}
function getPostsInDay( state: State, day: Day, mode: 'all' ): Post[] {
// ...
}
Easy, right? WRONG! This doesn’t work. According to TypeScript, we have a “duplicate function implementation.”
Okay, let’s try something different, then. Let’s merge the previous two definitions into a single function:
function getPostsInDay( state: State, day: Day, mode: 'id' | 'all' = 'id' ): PostId[] | Post[] {
if ( 'id' === mode ) {
return state.days[ day ] ?? [];
}
return [];
}
Does this behave as we want? I’m afraid it doesn’t…
Here’s what this function signature is telling us: “getPostsInDay
is a function that takes two arguments, a state
and a mode
whose values can either be id or all; its return type will either be a list of PostId
s or a list of Post
s.” In other words, the previous function definition doesn’t specify anywhere that there’s a relationship between the value given to the mode
argument and the function’s return type. And so code like this:
const state: State = { posts: {}, days: {} };
const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'id' );
const posts: Post[] = getPostsInDay( state, '2020-10-01', 'all' );
is valid and doesn’t behave like we want it to.
OK, last attempt. What if we mix our initial intuition, where we describe concrete function signatures, with a single, valid implementation?
function getPostsInDay( state: State, day: Day, mode: 'id' ): PostId[];
function getPostsInDay( state: State, day: Day, mode: 'all' ): Post[];
function getPostsInDay( state: State, day: Day, mode: 'id' | 'all' ): PostId[] | Post[] {
const postIds = state.days[ day ] ?? [];
if ( 'id' === mode ) {
return postIds;
}
return postIds
.map( ( pid ) => getPost( state, pid ) )
.filter( ( p ): p is Post => !! p );
}
The previous snippet has a valid function implementation that works, but defines two extra function signatures that bind concrete values in mode
with the function’s return type.
Using this approach, this code is valid:
const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'id' );
const posts: Post[] = getPostsInDay( state, '2020-10-01', 'all' );
and this one doesn’t:
const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'all' );
const posts: Post[] = getPostsInDay( state, '2020-10-01', 'id' );
Conclusions
In this series of posts we have seen what TypeScript is and how we can apply it in our projects. Types help us to better document the code by providing semantic context. Moreover, types also add an extra layer of security, since the TypeScript compiler takes care of validating that our code fits together correctly, just like Legos do.
At this point you already have all the necessary tools to take the quality of your work to the next level. Good luck in this new adventure!
Featured image by Mike Kenneally on Unsplash.
Leave a Reply