Introduction
This will be my first time writing about TS. There are multiple tutorials out there which explain the concept of discriminated unions in an outstanding way. But I will I try my best to explain here my understanding about the concept and how I approached it.
In this blogpost, we are going to learn about discriminated union types. We will also look at how it is an excellent concept that makes developer’s life less miserable. You will also get to see some realtime examples.
What are discriminated Union Types?
Let me be a good dev and explain this concept by explaining the problem and then the solution.
Problem Statement
An interface or a type can represent multiple states. And based on these states the other properties in the interface can differ. For example,
-
A network response has the following structure
// For successful request { "status": '200', "payload": {...} } // For failure in request { "status": '400', "error": {...} }
As you can see, based on the
status
property the rest/some of the properties might be present or may note be present. -
A general shape interface can have multiple properties that might not be present in all the shapes:
{ "type": 'Circle', "radius": 50 } { "type": 'Square', "sideLength": 10 }
Now their interfaces will look something like below:
Now suppose for the Response interface we want to write a utility function that logs the response in different format like below:
Both of these blocks will have access to the successPayload
and errorPayload
i.e. we know that type 200
means a successful request and we want only to get successPayload and nothing else. Similar is the case for the type 400
The mental model for the types we have is a mapping of status state i.e. type
value with the rest of the parameters.
As you can see this is a very common problem that all the developers face. To resolve this and tell typescript to distinguish between different values of types we need to make use of Discriminated union types of typescript.
Discriminated Union Types
This is a nice little feature that Typescript provides that helps the TS itself to distinguish between which rest of the parameters to choose based on a common property between them. Let us restructure our above example of NetworkResponse interface:
Now if we try use the displayNetworkResponse
function we won’t get any type errors.
Realtime Use case
I have always faced this issue while using the React’s Context APIs where I have my actions ready with me and each and every action would have a different payload
property. But when I am building my reducer function I don’t get the narrowed typing based on the action case I am in inside the switch case. Let me demonstrate this with an example.
Suppose you have these set of actions with you:
These are simple actions like adding and removing a TODO from a list
Next, we define the action creators or the functions that dispatch the action like below:
import { ACTIONS } from "./actions";
export const addTodo = (id: number, text: string) =>
({
type: ACTIONS.ADD_TODO,
payload: {
id,
text,
},
});
export const removeTodo = (id: number) =>
({
type: ACTIONS.REMOVE_TODO,
payload: {
id,
},
});
As we see here the payload
attribute of both the actions is different. For ADD_TODO
action the payload attribute consists of id
and text
and for REMOVE_TODO
action we just pass an id
attribute.
Now let us define our reducer function like below:
import { ACTIONS } from "./actions";
export const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.ADD_TODO: {
const { payload } = action;
payload.text;
}
case ACTIONS.REMOVE_TODO: {
const { payload } = action;
payload.id;
}
}
};
In both the action cases TS is not able to decide the types for the payload. It will display typings as follows:
const payload: {
id: number;
text: string;
} | {
id: number;
}
But this is not what we want. We expect that when the ADD_TODO
action is being used as the payload
attribute then it should consists of id
and text
attribute and for REMOVE_TODO
we expect that it should consists of only the id
attribute. How can typescript know this mapping? Discriminated Union Types to the rescue.
We need to return the ActionTypes
type from the actions file like below:
ActionTypes
here is a type that will be a union of return type of addTodo
and removeTodo
function. Here the common property to discriminated will be the type
property in both function’s return type. Now making use of this type inside the reducer
function like below, helps us to achieve what we wanted:
Now if you check the payload attribute in each section then we are able to see that payload types are getting changed based on the action type.
So this is the magic of Discriminated Union Types.
Summary
I feel discriminated union type is the most underrated feature for typescript. I believe every developer should use it to have better type narrowing that will help to write better and predictable code.
Thanks a lot for reading my blogpost.
Top comments (0)