TypeScript 3.7 now supports the optional chaining operator. Hence, you can write code such as:
const value = a?.b?.c;
I.e., you can use this operator to access properties of an object, where the object itself may be null or undefined. Now what I would like to do is basically the same, but the property names are dynamic:
const value = a?[b]?.c;
However, there I get a syntax error:
error TS1005: ':' expected.
What am I doing wrong here? Is this even possible?
The proposal seems to imply that this is not possible (but maybe I get the syntax examples wrong).
When accessing a property using bracket notation and optional chaining, you need to use a dot in addition to the brackets:
const value = a?.[b]?.c;
This is the syntax that was adopted by the TC39 proposal, because otherwise it's hard for the parser to figure out if this ? is part of a ternary expression or part of optional chaining.
The way I think about it: the symbol for optional chaining isn't ?, it's ?.. If you're doing optional chaining, you'll always be using both characters.
The Optional Chaining operator is ?.
Here are some examples for nullable property and function handling.
const example = {a: ["first", {b:3}, false]}
// Properties
example?.a // ["first", {b:3}, false]
example?.b // undefined
// Dynamic properties ?.[]
example?.a?.[0] // "first"
example?.a?.[1]?.a // undefined
example?.a?.[1]?.b // 3
// Functions ?.()
null?.() // undefined
validFunction?.() // result
(() => {return 1})?.() // 1
Bonus: Default values
?? (Nullish Coalescing) can be used to set a default value if undefined or null.
const notNull = possiblyNull ?? defaultValue
const alsoNotNull = a?.b?.c ?? possiblyNullFallback ?? defaultValue
Related
TypeScript 3.7 now supports the optional chaining operator. Hence, you can write code such as:
const value = a?.b?.c;
I.e., you can use this operator to access properties of an object, where the object itself may be null or undefined. Now what I would like to do is basically the same, but the property names are dynamic:
const value = a?[b]?.c;
However, there I get a syntax error:
error TS1005: ':' expected.
What am I doing wrong here? Is this even possible?
The proposal seems to imply that this is not possible (but maybe I get the syntax examples wrong).
When accessing a property using bracket notation and optional chaining, you need to use a dot in addition to the brackets:
const value = a?.[b]?.c;
This is the syntax that was adopted by the TC39 proposal, because otherwise it's hard for the parser to figure out if this ? is part of a ternary expression or part of optional chaining.
The way I think about it: the symbol for optional chaining isn't ?, it's ?.. If you're doing optional chaining, you'll always be using both characters.
The Optional Chaining operator is ?.
Here are some examples for nullable property and function handling.
const example = {a: ["first", {b:3}, false]}
// Properties
example?.a // ["first", {b:3}, false]
example?.b // undefined
// Dynamic properties ?.[]
example?.a?.[0] // "first"
example?.a?.[1]?.a // undefined
example?.a?.[1]?.b // 3
// Functions ?.()
null?.() // undefined
validFunction?.() // result
(() => {return 1})?.() // 1
Bonus: Default values
?? (Nullish Coalescing) can be used to set a default value if undefined or null.
const notNull = possiblyNull ?? defaultValue
const alsoNotNull = a?.b?.c ?? possiblyNullFallback ?? defaultValue
So, I am reading optional chaining in JavaScript and a question popped into my head.
Consider the code below:
let person = null
let street = person?.street // returns undefined
My question is that, if the variable person is null to begin with, then why is doing optional chaining result to setting the variable as undefined and not null?
If the person was undefined, then I guess, for me, setting it to undefined is reasonable because the variable is of course undefined, like the below:
let person
let street = person?.street // returns undefined
PS: I'm sorry if this is a stupid question, I'll delete it if someone agrees. :)
PPS: If this question is duplicated, pls drop the link and I'll give it a go. Thank you, very much.
An optional chain does not evaluate to the nullish value on which the property was accessed, but to undefined - just like you get undefined when a property does not exist in an object.
Conceptually, think of person?.street not as
person && person.street
but rather as
(person ?? {}).street
Though accurately (especially when chained further, since it does short-circuit instead of evaluating the rest of the chain) it's really
person != null ? person.street : undefined
See also the FAQ on the optional chaining proposal:
Why does (null)?.b evaluate to undefined rather than null?
Neither a.b nor a?.b is intended to preserve arbitrary information
on the base object a, but only to give information about the
property "b" of that object. If a property "b" is absent from a,
this is reflected by a.b === undefined and a?.b === undefined.
In particular, the value null is considered to have no properties;
therefore, (null)?.b is undefined.
Please read the manual about optional chaining.
The ?. operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.
The reason the result is undefined and not null is because of optional chaining (?.).
If optional chaining wasn't available, your code would've resulted in an error, saying that null has no properties.
const person = null;
person.street // ERROR!
But, since you have optional chaining, it only results in undefined.
const person = null;
person?.street // undefined
This is why optional chaining results in undefined (if the value is null or undefined); to avoid throwing an error!
Hope this answered your question!
In our code, I have two statements
const { column, showTooltip, tooltipValue, data } = props;
const key = column.bindProperties[0].properties[0].name;
on testing, this gives error as
"TypeError: Cannot read property '0' of undefined."
what is the meaning of this statement column.bindProperties[0].properties[0].name; and how to test it.
In JS you can't guarantee that objects have certain properties.
When you try to access column.bindProperties[0].properties[0].name, either column.bindProperties or column.bindProperties[0].properties is undefined - hence the error you're getting.
You can either use lodash's _.get() or validate the keys are defined using the redundantly annoying:
const key = column
&& column.bindProperties
&& column.bindProperties[0]
&& column.bindProperties[0].properties
&& column.bindProperties[0].properties[0]
&& column.bindProperties[0].properties[0].name;
This will make sure your code won't break. If one expression in the chain isn't defined, the expression will stop evaluating and you'll just get undefined as the result.
Since no one has really just spelt it out, here's an example of optional chaining:
const key = column?.bindProperties?.[0]?.properties?.[0]?.name;
and with nullish coalescing:
const key = column?.bindProperties?.[0]?.properties?.[0]?.name ?? "I'm a fallback value";
I'm trying to use optional chaining with an array instead of an object but not sure how to do that:
Here's what I'm trying to do myArray.filter(x => x.testKey === myTestKey)?[0].
Also trying similar thing with a function:
let x = {a: () => {}, b: null}
console.log(x?b());
But it's giving a similar error - how can I use optional chaining with an array or a function?
You need to put a . after the ? to use optional chaining:
myArray.filter(x => x.testKey === myTestKey)?.[0]
Playground link
Using just the ? alone makes the compiler think you're trying to use the conditional operator (and then it throws an error since it doesn't see a : later)
Optional chaining isn't just a TypeScript thing - it is a finished proposal in plain JavaScript too.
It can be used with bracket notation like above, but it can also be used with dot notation property access:
const obj = {
prop2: {
nested2: 'val2'
}
};
console.log(
obj.prop1?.nested1,
obj.prop2?.nested2
);
And with function calls:
const obj = {
fn2: () => console.log('fn2 running')
};
obj.fn1?.();
obj.fn2?.();
Just found it after a little searching on the what's new page on official documentation
The right way to do it with array is to add . after ?
so it'll be like
myArray.filter(x => x.testKey === myTestKey)?.[0]
I'll like to throw some more light on what exactly happens with my above question case.
myArray.filter(x => x.testKey === myTestKey)?[0]
Transpiles to
const result = myArray.filter(x => x.testKey === myTestKey) ? [0] : ;
Due to which it throws the error since there's something missing after : and you probably don't want your code to be transpilled to this.
Thanks to Certain Performance's answer I learned new things about typescript especially the tool https://www.typescriptlang.org/play/index.html .
ECMA 262 (2020) which I am testing on Edge Chromium 84 can execute the Optional Chaining operator without TypeScript transpiler:
// All result are undefined
const a = {};
console.log(a?.b);
console.log(a?.["b-foo-1"]);
console.log(a?.b?.());
// Note that the following statements throw exceptions:
a?.(); // TypeError: a is not a function
a?.b(); // TypeError: a?.b is not a function
CanIUse: Chrome 80+, Firefox 74+
After a bit of searching the new page in the official documentation, it was discovered.
You need to put a . after the ? to use optional chaining.
So it will be so,
myArray.filter(x => x.testKey === myTestKey)?.[0]
Used only ? Makes the compiler think that you are trying to use a conditional operator (then it causes an error because it doesn't see a : later)
It's not necessary that the function is inside the object, you can run a function using optional chaining also like this:
someFunction?.();
If someFunction exists it will run, otherwise it will skip the execution and it will not error.
This technique actually is very useful especially if you work with reusable components and some components might not have this function.
Well, even though we figured out the correct syntax, the code doesn't make much sense to me.
The optional chaining in the code above is making sure, that the result of myArray.filter(x => x.testKey === myTestKey) is not null and not undefined (you can have a look at the TS output). But it is not possible anyway, because the result of the filter method is always an array. Since JavaScript doesn't throw "Array bounds exceeded", you are always safe when you try to access any index - you will get undefined if this element doesn't exist.
More example to make it clear:
const myArray: string[] = undefined
console.log(myArray.filter(x => x)?.[0]) //throws Cannot read property 'filter' of undefined
//in this example the optional chaining protects us from undefined array
const myArray: string[] = undefined
console.log(myArray?.filter(x => x)[0]) //outputs "undefined"
I'm trying to use the Typescript optional chaining operator but it threw this exception:
index.ts:6:1 - error TS2779: The left-hand side of an assignment
expression may not be an optional property access.
My sample code:
const url = URI({
protocol: 'http',
hostname: 'example.org'
})
// This line threw
document.getElementById('output')?.innerHTML = url.toString()
How to resolve this problem?
objectVariableName!.propertyName = 'some value to assign';
Please note the exclamation symbol i.e,!
const output = document.getElementById('output');
if (output) output.innerHTML = url.toString()
This operator is made for accessing deep nest values.
Let's look at document.getElementById('output')?.innerHTML. This will return undefined (if '#output' not exists) or string (if '#output' exists). And you trying to assign string to it.
Here you are trying to set a new value for an object property that may not exist.
So yep, optional property access can not be used at the left-hand side of an assignment.
You can read more about it in proposal
You can also solve this with an early return:
const output = document.getElementById('output');
if (!output) return;
output.innerHTML = url.toString()
Use it like this for nested properties:
if (!item?.text) return;
item.text.text = action.payload.text;
https://medium.com/swlh/return-early-pattern-3d18a41bba8
https://softwareengineering.stackexchange.com/questions/18454/should-i-return-from-a-function-early-or-use-an-if-statement
As mentioned here:
The Document method getElementById() returns an Element object representing the element whose id property matches the specified string.
If we go and see what properties the Element base class contains, you will see innerHTML.
This means that it is sure that an instance of Element(the result of getElementById) will have an innerHTML property, which is why you're getting the error.
This very short expression works for me nicely in typescript 4.0.3
let domEl: HTMLElement | null = document.querySelector("#app");
domEl && (domEl.style.color = "green");
in ES12 you can do this with Logical nullish assignment
document.getElementById('output')?.innerHTML ??= url.toString()
so the assignment will happen only if the left-hand expression is not nullish.
this is just like if you would do
if (document.getElementById('output')?.innerHTML) {
document.getElementById('output').innerHTML = url.toString()
}
My exception was :
The left-hand side of an assignment expression may not be optional property access.
I received this error in typescript "~4.6.2" and solved it like.
let headingDom : HTMLElement | null = document?.querySelector('h1');
if(headingDom) headingDom.textContent = 'Hello World';