I primarily work with React and often find that when I write a function that relies on a ponent's state, I have to perform a check to see if the piece of state is defined before performing any actions.
For example: I have a function that uses .map()
to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render()
function of my ponent. The first time render()
is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.
I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.
I primarily work with React and often find that when I write a function that relies on a ponent's state, I have to perform a check to see if the piece of state is defined before performing any actions.
For example: I have a function that uses .map()
to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render()
function of my ponent. The first time render()
is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.
I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.
Share Improve this question asked Oct 3, 2018 at 18:30 Chris VossChris Voss 7982 gold badges11 silver badges23 bronze badges 1- Possible duplicate of How to avoid 'cannot read property of undefined' errors? – jo_va Commented Feb 20, 2019 at 10:12
2 Answers
Reset to default 8Check the array before using map:
arr && arr.map()
OR,
arr && arr.length && arr.map() // if you want to map only if not empty array
OR,
We can even use like this (as mented by devserkan):
(arr || []).map()
As per your ment:
I wish there was a safe navigation operator like with C# (arr?.map())
Yes, obviously. This is called optional chaining in JavaScript which is still in proposal. If it is accepted, then you may use like this:
arr?.map()
You can see it in staging 1 for which you may use babel preset stage1
But obviously, except the checking array length, your requirement will not be fulfilled:
This results in an error because, of course, the first index of the array is undefined.
So, I suggest you to use:
arr && arr.length && arr.map()
What you actually need here is called optional chaining:
obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null
The ?.
is the existential operator and it allows you to access properties safely and won't throw if the property is missing. However optional chaining is not yet part of JavaScript but has been proposed and is in stage 3, see State 3 of TC39.
But, using proxies and a Maybe
class, you can implement optional chaining and return a default value when the chain fails.
A wrap()
function is used to wrap objects on which you want to apply optional chaining. Internally, wrap
creates a Proxy around your object and manages missing values using a Maybe
wrapper.
At the end of the chain, you unwrap the value by chaining getOrElse(default)
with a default value which is returned when the chain is not valid:
const obj = {
a: 1,
b: {
c: [4, 1, 2]
},
c: () => 'yes'
};
console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
The plete example:
class Maybe {
constructor(value) {
this.__value = value;
}
static of(value){
if (value instanceof Maybe) return value;
return new Maybe(value);
}
getOrElse(elseVal) {
return this.isNothing() ? elseVal : this.__value;
}
isNothing() {
return this.__value === null || this.__value === undefined;
}
map(fn) {
return this.isNothing()
? Maybe.of(null)
: Maybe.of(fn(this.__value));
}
}
function wrap(obj) {
function fix(object, property) {
const value = object[property];
return typeof value === 'function' ? value.bind(object) : value;
}
return new Proxy(Maybe.of(obj), {
get: function(target, property) {
if (property in target) {
return fix(target, property);
} else {
return wrap(target.map(val => fix(val, property)));
}
}
});
}
const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2