It is a very common error when working with object and array to get a TypeError: Cannot read property 'name' of undefined
. This happens when we try to access a property of a value that is undefined or null in JavaScript.
var movie = {
name: 'Interstellar',
director: {
name: 'Christopher Nolan',
born: 'July 30, 1970',
},
music: 'Hans Zimmer',
}
console.log(movie.director.name) //Christopher Nolan
console.log(movie.music.name) //undefined
console.log(movie.cast[0].name) //Uncaught TypeError: Cannot read property '0' of undefined
var obj = {}
console.log(obj.prop1.deepProp) //Uncaught TypeError: Cannot read property 'deepProp' of undefined
There are some ways to avoid this kind of errors.
1. The simplest approach is using the logical AND operator &&
.
var obj = {}
console.log(obj.prop1.deepProp) //Uncaught TypeError: Cannot read property 'deepProp' of undefined
console.log(obj.prop1 && obj.prop1.deepProp) //undefined
This works because the &&
operators actually return one of the value of the specified operands if these operators are used with non-Boolean values. The rule is described as. If we had expr1 && expr2
, it will
See more on Expressions and operators
The obj.prop1 && obj.prop1.deepProp
returns undefined
because obj.prop1
is undefined which will be converted/coerced to false
. The reason behind how JavaScript uses type coercion in Boolean contexts. You can read more, Truthy.
I often use the &&
to check If the property is actually existed before going down further.
//good
if (obj.prop1 && obj.prop1.deepProp) doSomething()
//error prone
if (obj.prop1.deepProp)
//Uncaught TypeError: Cannot read property 'deepProp' of undefined
doSomething()
It works well for small chains of properties, but getting ugly very soon If we are going too deep into a property.
var obj = {
prop1: {},
}
let neededProperty = obj.prop1.deepProp.veryVeryDeepProp
//Uncaught TypeError: Cannot read property 'veryVeryDeepProp' of undefined
let deepProperty = obj.prop1 && obj.prop1.deepProp.veryDeepProp.veryVeryDeepProp
//Uncaught TypeError: Cannot read property 'veryDeepProp' of undefined
//Same error as above because prop1 is an object which will be coerced to true so that
//obj.prop1.deepProp.veryVeryDeepProp operand will be evaluated and throw and error
let safeProperty =
obj.prop1 &&
obj.prop1.deepProp &&
obj.prop1.deepProp.veryDeepProp &&
obj.prop1.deepProp.veryVeryDeepProp
//undefined
//This is a very safe check but the code will be messy.
2. Using try/catch blocks.
Because some things can go wrong…
try {
let deepProperty =
obj.prop1 && obj.prop1.deepProp.veryDeepProp.veryVeryDeepProp
} catch (exception) {
// do something else
}
But placing many try/catch blocks throughout your code just to access properties is neither practical nor readable.
3. Helper function
We will write a simple helper function that does nothing else but calling a provided function in a try/catch block. With the support of ES6 Arrow function, we can call the helper function in just a one-line callback.
var obj = {}
function getSafe(fn) {
try {
return fn()
} catch (e) {
console.log(e)
return undefined
}
}
// use it like this
let deepProperty = getSafe(
() => obj.prop1.deepProp.veryDeepProp.veryVeryDeepProp
)
That way, you will either get the property value or undefined. That works because JavaScript only evaluates the content of the inner callback (or closure) when it is actually executed. So it will only be evaluated inside of the try/catch block and not yet in the code calling the helper function. This version down below will never work because the property argument will be evaluated when we pass it into the function.
var obj = {}
function getSafeNaive(property) {
try {
return property
} catch (e) {
return undefined
}
}
let deepProperty = getSafeNaive(
obj.prop1.deepProp.veryDeepProp.veryVeryDeepProp
)
//error from here, so that cannot go inside the try catch block
Some programming languages also support the so called elvis operator ?.
that is yet another approach to the same problem. Applying it to the dirty example from above would like this obj?.prop1?.deepProp?.veryDeepProp?.veryVeryDeepProp
. Essentially, it makes the compiler stop accessing more nested properties as soon as one of them is null (or undefined, or whatever null-type(s) that language uses).
Currently, there is no elvis operator in neither JavaScript nor TypeScript.
In Angular, there is the support elvis operator to protect against a view render failure. They call it safe navigation operator. Take the example below
The current person name is {{nullObject?.name}}
Since it is trying to access name
property of a null value, the whole view disappears and you can see the error inside the browser console. It works perfectly with long property paths such as a?.b?.c?.d
. So I recommend you to use it everytime you need to access a property inside a template.
After a long wait, finally TypeScript has brought this feature to live. It basically is the elvis operator above.
At its core, optional chaining lets us write code where TypeScript can immediately stop running some expressions if we run into a null or undefined. The star of the show in optional chaining is the new ?. operator for optional property accesses. When we write code like
let x = foo?.bar.baz()
this is a way of saying that when foo is defined, foo.bar.baz() will be computed; but when foo is null or undefined, stop what we’re doing and just return undefined.”
let x = foo === null || foo === undefined ? undefined : foo.bar.baz()
Note that if bar
is null or undefined, our code will still hit an error accessing baz. Likewise, if baz
is null or undefined, we’ll hit an error at the call site. ?. only checks for whether the value on the left of it is null or undefined - not any of the subsequent properties.
You might find yourself using ?. to replace a lot of code that performs repetitive nullish checks using the && operator.
// Before
if (foo && foo.bar && foo.bar.baz) {
// ...
}
// After-ish
if (foo?.bar?.baz) {
// ...
}
Keep in mind that ?.
acts differently than those &&
operations since &&
will act specially on “falsy”
values (e.g. the empty string, 0, NaN, and, well, false), but this is an intentional feature of the construct. It doesn’t short-circuit on valid data like 0 or empty strings.