Null or undefined handling appears many times. Do you have an interface with the nullable property but want to reuse it to create another data type?
Non Null Assertion Operator (Exclamation mark operator)
Array.prototype.find()
can return undefined
so we need to check if it is not undefined
. If you are sure that the result is not undefined you can use non-null assertion.
let array = [
{ key: "key1", value: 11 },
{ key: "key2", value: 22 },
{ key: "key3", value: 33 },
{ key: "key4", value: 44, special: { value: true } },
{ key: "key5", value: 55 },
];
const find = (val: number) => array.find((x) => x.value === val);
const result1 = find(22)
console.log(result1!.key);
// key2
But be careful to use it because it throws the following error when it is undefined
.
TypeError: Cannot read property 'key' of undefined
Optional chaining Operator (Question mark operator)
Optional chaining is safer than non-null assertion because it returns undefined
when the variable is undefined
. It doesn’t try to call chained property or function.
const result2 = find(2233);
console.log(result2?.key);
// undefined
const result3 = find(33);
console.log(result3?.key);
// key3
Optional chaining can of course be chained as many as we want.
let array = [
{ key: "key1", value: 11 },
{ key: "key2", value: 22 },
{ key: "key3", value: 33 },
{ key: "key4", value: 44, special: { value: true } },
{ key: "key5", value: 55 },
];
const existSpecial = (value: number) => {
const found = find(value);
if (found?.special?.value === true) {
console.log(`special object found.`);
} else {
console.log(`special object not found.`);
}
}
existSpecial(22);
// special object not found.
existSpecial(44);
// special object found.
Reuse an existing definition to create another type
Sometimes we want to create another interface that has the same properties as existing one but all properties are non null. Using extends
keyword is one of the solutions for that but Typescript offers another way. Following is a base interface definition. We’ll use this to create other types.
interface MyType {
name: string;
age: number;
hobby?: string;
}
const myType1: MyType = { name: "yu", age: 34 };
const myType2: MyType = { name: "yu", age: 34, hobby: "programming" };
Make all properties mandatory (Required)
We can reuse this definition to create another interface or type. Required
makes all properties mandatory.
type AllPropsRequiredType = Required<MyType>;
const myType3: AllPropsRequiredType = { name: "yu", age: 34, hobby: "programming" };
const myType4: AllPropsRequiredType = { name: "yu", age: 34 }; // error
// Property 'hobby' is missing in type '{ name: string; age: number; }' but required in type 'Required<MyType>'.ts(2741)
Make all properties nullable (Partial)
In opposite to Required
above, we can make all properties optional.
type PartialType = Partial<MyType>;
const partialType1: PartialType = {};
Omit unnecessary properties (Omit)
If the nullable property is unnecessary we can remove it from the definition.
type OmitType = Omit<MyType, "hobby">;
const omitType1: OmitType = { name: "yu", age: 34 };
const omitType2: OmitType = { name: "yu", age: 34, hobby: "error" }; // error
// Type '{ name: string; age: number; hobby: string; }' is not assignable to type 'OmitType'.
// Object literal may only specify known properties, and 'hobby' does not exist in type 'OmitType'.ts(2322)
Pick necessary properties (Pick)
In opposite to Omit
above, we can pick only the necessary properties.
type PickType = Pick<MyType, "name" | "hobby">;
const pickType1: PickType = { name: "yu" };
const pickType2: PickType = { name: "yu", hobby: "programming" };
This post might be helpful for a nested object.
Change the type to Non Nullable (NonNullable)
If a type allows null
and undefined
but want to change it non-nullable type.
type NullableDataType = number | string | null | undefined;
const nullableData1: NullableDataType = null;
const nullableData2: NullableDataType = undefined;
const nullableData3: NullableDataType = "string";
type NonNullableType = NonNullable<NullableDataType>;
const nonNullableData1: NonNullableType = null; // error
// Type 'null' is not assignable to type 'NonNullableType'.ts(2322)
const nonNullableData2: NonNullableType = undefined; // error
// Type 'undefined' is not assignable to type 'NonNullableType'.ts(2322)
const nonNullableData3: NonNullableType = "string";
Set Optional or Mandatory
If you want to set some properties optional or mandatory, you can check this post too. In this post, Optional and Mandatory utilities are defined.
Null handling class to remove null check
We sometimes face a case that we need to put a null check in many places for the same object/class. Null check logic makes our code messy so it’s better to reduce it. Here is a small example.
interface Role {
do(): void;
}
class Manager implements Role {
do(): void {
console.log("I make a plan.");
}
}
class Developer implements Role {
do(): void {
console.log("I develop a software.");
}
}
function roleFactory(value: string): Role | undefined {
switch (value) {
case "manager": return new Manager();
case "developer": return new Developer();
default: return undefined;
}
}
const roles = ["manager", "developer", "sales"];
roles.forEach((role) => {
const instance = roleFactory(role);
if (instance) {
instance.do();
}
});
A class for “sales” is not defined so roleFactory
returns undefined
. We have to check if the instance is not undefined to call do
function. It’s ok for this small example to check whether it’s undefined or not. However, what if there are other functions in the interface and roleFactory
is called in different places? Null check logic appears in those places as well. Are there good ways for it?
Create a class to handle undefined/null and do nothing in it.
class UndefinedRole implements Role {
do(): void { }
}
function roleFactory(value: string): Role {
switch (value) {
case "manager": return new Manager();
case "developer": return new Developer();
default: return new UndefinedRole();
}
}
const roles = ["manager", "developer", "sales"];
roles.forEach((role) => {
const instance = roleFactory(role);
instance.do();
});
No check logic is needed in this way. Return an empty array if the return type is an array.
Comments