Revisiting Conditionals in JavaScript and TypeScript
This post is part of the Mastering JavaScript Series.
We are going to see different ways we can handle conditions in JavaScript and how TypeScript can help us make better use of code.
Imagine we have a boolean value and based on this boolean, we want to assign a value to a new variable.
const isActive = true;
With this boolean, we want:
- if active (
isActive
= true): assign a valueon
to the variabletoggle
. - if inactive (
isActive
= false): assign a valueoff
to the variabletoggle
.
let toggle;
if (isActive) {
toggle = 'on';
} else {
toggle = 'off';
}
To do this, we usually use a var
or a let
statement. Create a toggle
with undefined
value and then assign the correct value based on the isActive
value.
This works.
But we can't use const
in this case. When defining a const
, we need to add a value attached to it. Doing something like this will throw an error:
> Uncaught SyntaxError: Missing initializer in const declaration
We also can't use const
inside the if-else condition.
If we do this:
if (isActive) {
const toggle = 'on';
} else {
const toggle = 'off';
}
And then verify the toggle
value, it throws an error because this constant is not in the scope.
$ toggle
> Uncaught ReferenceError: toggle is not defined
Another way to handle this type of condition is by using the ternary operator.
const toggle = isActive ? 'on' : 'off';
That's nice and beautiful. Capture everything in a very short and readable way.
Now imagine handling multiple conditions. We can't really use the ternary operator. The first thought is to come back to the if-else statement, but now with multiple possible conditions:
let label;
if (status === 'finished') {
label = 'Finished task';
} else if (status === 'inactive') {
label = 'Task inactive';
} else if (status === 'ongoing') {
label = 'Ongoing task';
}
Another possibility that comes to mind is using a switch case.
let label;
switch (status) {
case 'finished':
label = 'Finished task';
break;
case 'inactive':
label = 'Task inactive';
break;
case 'ongoing':
label = 'Ongoing task';
break;
}
But what if we also want to assign a value to another variable? A tag
variable in this case. The tag's value follows this logic:
finished
:Finished
inactive
:Inactive
ongoing
:Ongoing
Let's build it!
let label;
let tag;
switch (status) {
case 'finished':
label = 'Finished task';
tag = 'Finished';
break;
case 'inactive':
label = 'Task inactive';
tag = 'Inactive';
break;
case 'ongoing':
label = 'Ongoing task';
tag = 'Ongoing';
break;
}
Now we also want a button's variant for each status. The logic follows:
finished
:secondary
inactive
:disabled
ongoing
:primary
Let's add this variable to the switch case.
let label;
let tag;
let variant;
switch (status) {
case 'finished':
label = 'Finished task';
tag = 'Finished';
variant = 'secondary';
break;
case 'inactive':
label = 'Task inactive';
tag = 'Inactive';
variant = 'disabled';
break;
case 'ongoing':
label = 'Ongoing task';
tag = 'Ongoing';
variant = 'primary';
break;
}
The lesson here is that the switch case starts to get bigger and more complex. To abstract this complexity, we can use object to map the status to an object that represents the status.
const statusMap = {
finished: {
label: 'Finished task',
tag: 'Finished',
variant: 'secondary',
},
inactive: {
label: 'Task inactive',
tag: 'Inactive',
variant: 'disabled',
},
ongoing: {
label: 'Ongoing task',
tag: 'Ongoing',
variant: 'primary',
},
};
const { label, tag, variant } = statusMap['finished'];
label; // => Finished tag
tag; // => Finished
variant; // => secondary
And if you are using a type system like TypeScript, we can do even better things.
We can type the statusMap
’s key and value and require to use the existing keys.
type Statuses = 'finished' | 'inactive' | 'ongoing';
type StatusObject = {
label: string;
tag: string;
variant: string;
};
type StatusMap = Record<Statuses, StatusObject>;
And we used in the map:
const statusMap: StatusMap = {
finished: {
label: 'Finished task',
tag: 'Finished',
variant: 'secondary',
},
inactive: {
label: 'Task inactive',
tag: 'Inactive',
variant: 'disabled',
},
ongoing: {
label: 'Ongoing task',
tag: 'Ongoing',
variant: 'primary',
},
};
When you use it (and if your editor is configured to make the IntelliSense works), it will show all the possibilities for you.
It will also get errors in compile-time if you use a different key to access the object.
Great! Now we have a solution abstracting the complexity and getting errors in compile-time. In the future, it will also be possible to use pattern matching in JavaScript and we can come up with more solutions to handle conditions.