-Enables to immediately return undefined in object chaining if a null or undefined is encountered.
?. is the symbol to achieve this.
Example #1
const customer :{
customerId: number,
firstName?: string,
lastName?:string,
} = { customerId: 4, FirstName: 'Daniel' };
const firstName = customer?.firstName?.toUpperCase(); // Now we can do the optional chaining
console.log(firstName);In the example above, customer is checked for null or undefined; and since it is not, firstName is checked for null or undefined, and since it is not, it will execute toUpperCase(). The console log in the above example will print out 'DANIEL'.
Now let's see the following example Example #2
const lastName = customer?.lastName?.toUpperCase();
console.log(lastName);In this example, customer is checked for null or undefined. Since it is not, the execution of the chain will continue and check lastName for null or undefined. Here since lastName is not set, it is undefined, the optional chain will return immediately undefined without continuing the execution ( toUpperCase() will not be called) and the console output will be 'undefined'
The following codes are the equivalent of Example #1 Equivalent Code #1
let firstName = undefined;
if(customer){
if(customer.firstName){
firstName = customer.firstName.toUpperCase()
}
}
console.log(firstName)Equivalent Code # 2
const firstName = customer && customer.firstName && customer.firstName.toUpperCase();How many places we use the && to check null and undefined? so Generally speaking, we can replacing all such repetitive code with &&.
There is a difference between && and ?. and proceed with caution
&& evaluates falsy values such as empty, 0, false, and NaN. However, if ?. halts the chain and returns undefined if only encountered with null or undefined. It does not return or stop execution if encountered valid values such as 0 or empty
Example #3
const fullName: {firstName:string, lastName?:string} = {firstName:"Alex",lastName: ""};
// using `?.`
let defaultLastName = fullName?.lastName?.replace("","Maru");
console.log(`LastName(?.): ${defaultLastName}`); // will print 'LastName: Maru'
//using &&
defaultLastName = fullName && fullName.lastName && fullName.lastName.replace("","Bontu");// string is immutable, so lastName is still empty
console.log(`LastName(&&):${defaultLastName}`); // will print undefined
From the above example, it is clear that && and ?. act differently on 'empty' values. This is intentional and just you need to be careful when replacing && with ?.
?. can also be used with array indexing, and function calls as well
Example #4 - Array accessing
const oddNumbers = [3,5,9];
const firstValue = oddNumbers?.[0];
console.log(firstValue);On oddNumbers?.[0], ?. checks if the array is null or empty, otherwise access the first element.
Example #5 - Function Calls
function calculate(x:number,y:number,op?:(p:number,q:number)=>number):number|undefined{
return op?.(x, y); //checks if op is not null or undefined,
}
let sum = calculate(4,5,(x,y) => x+y ); //sum will be 9
console.log(sum);
sum = calculate(4,5); //sum will be 'undefined'In Example #5, op?.(x,y) checks if the function parameter op is null or undefined, if not, it will execute the method call otherwise returns undefined
?. only acts on object chaining and does not act on the entire statement of an expression. it means after it shortcircuits on null or undefined , it won't stop what follows the chaining.
Example #6
const coordinates:{x:number, y:number, z?:number} = { x:3, y: 5};
const multiplied = coordinates?.z * 4 ; //errorIn Example #6, ?. returns undefined on evaluating coordinates?.z but does not stop the rest of the statement, thus the above code will result in compile time error 'Object is possibly 'undefined', saved by the awesomeness of typescript.
However, if you are concatening strings to create a message to a user, be careful since you might concatenate empty string with some other string and the result may not be as expected.
?? now has become to state a default fall back when null or undefined is encountered.
Example #7
function execute(firstName: string, greeting?: string) {
const defaultGreeting = "What's up!";
return `${firstName}, ${greeting ?? defaultGreeting}`;
}
console.log(execute('Daniel'));
console.log(execute('Daniel', 'Good morning!');In the code snippet above, the first console log results in 'Daniel, What's up!' and the second one results in 'Daniel, Good morning!'
?? can be used to replace ||. However, || acts on falsy values such as empty (""), undefined, 0, NaN. ?? acts on undefined or null.
Example #7
const defaultSpeedBy = 20;
const currentSpeed = 10;
let speedBy:number|undefined = undefined;
const newSpeedCorrect = currentSpeed + (speedBy||defaultSpeedBy); // 30
const newSpeedAlsoCorrect =currentSpeed + (speedBy??defaultSpeedBy); //30
//now
speedBy = 0;
const newSpeedWrong = currentSpeed + (speedBy ||defaultSpeedBy) ;//30
const speedCorrect =currentSpeed + (speedBy ?? defaultSpeedBy) //10In the example, when speedBy is undefined both || and ?? give correct results. However, when speedBy is set to 0, || 0 is falsy value and results in wrong result 30 (newSpeedWrong). To avoid such erros, you can use ?? (speedCorrect)
Kudos to optional chaining and nullish coalescing.