I am getting into the habit of, depending on the context, converting some of my for loops to use array.find(). In so doing, I'm wondering if there's a way I can chain another operator on after the .find() in order to limit how much I grab from the object.
For instance, see the following:
currentStage = customerDoc.history.find(h => h.completed === false);
currentStageName = currentStage.name;
Since all I really want from here is the value for "currentStage.name", is there a way I can get this by chaining on after my find(), to specify I just want this property? If not, is there another way to do this in one line?
Yes you can like this, notice the use of || {} to avoid exception in case the find returns undefined
currentStage = (customerDoc.history.find(h => h.completed === false) || {}).name
But IMO you should keep it like you have right now, it's readable and easy to maintain
currentStage = customerDoc.history.find(h => h.completed === false);
currentStageName = currentStage && currentStage.name;
You could use optional chaining (which is currently a stage 3 TC39 proposal and not yet implemented in browsers) but can be used right now using babel's plugin and use it as such :
const currentStageName = customerDoc.history.find(h => !h.completed)?.name;
Use short-circuit evaluation to have a default object in case nothing if found, and destructuring to get the property you want. If nothing is found, the result would be undefined:
const { name: currentStageName } = customerDoc.history.find(h => h.completed === false) || {};
you could also chain a .map onto the find results in order to limit and/or reshape what gets returned by wrapping it in an array (and using filter if no results are found), e.g.
currentStage = [customerDoc.history.find(h => h.completed === false)].filter(h => h != undefined).map(h => ({'name':h.name}))[0]
Related
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 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"
What would be the best approach to check if all the elements of a given array are contained by another array? For example:
match(['countryaarea', 'countrybarea'], ['countrya', 'countryb']) // true
I have tried indexOf() and includes(), but it does not work.
You can use every and some methods.
const arr = ['countryaarea', 'countrybarea'];
function match(array, searchArray) {
if (array.length !== searchArray.length) {
return false;
}
return array.every(el => searchArray.some(sEl => el.includes(sEl)));
}
console.log(match(arr, ['countrya', 'countryb'])); // returns true
console.log(match(arr, ['countrya'])) // returns false
console.log(match(arr, ['abcd'])) // returns false
console.log(match(arr, [])) // returns false
You would also want to check for null values.
... best approach...
The 'best approach' is robust code that handles edge cases. But the OP is insufficient to know what the edge cases are. That's addressed below but first, the code.
function isSubset ( subset, reference ) {
if ( !Array.isArray( subset ) || !Array.isArray( reference )) return false;
return subset.every( element => reference.includes( element ));
}
The 'best approach' for coding:
generally almost never involves for loops. Less readable and more error prone than Iterator functions. And as seen in the other answers, for loops put artificial constraints on the problem.
Code composition using functions is in keeping with Javascript language design, functional programming paradigm, OO principles, and clean code in general.
The 'best approach' depends on desired functionality:
Must the arrays be the same length? All answers (as of this writing) assumes that.
Must the test-array be shorter and/or equal length of the reference array?
Can either or both arrays have duplicate items?
Is this throw-away code ?
Is this destined for a library or at least production code?
What if there are mixed types in either or both arrays?
If this is simply "git er done", isolated use then verbose-but-understood code is OK and non-robust parameter checking is OK.
My assumptions
Return false for failed parameter checks, don't just blow up.
Both things must be arrays.
No constraints on array length.
Duplicate elements in either array do not need item-for-item duplication in the other.
An empty array is a subset of any other array including another empty array.
String compare is case sensitive.
I did not want to get wrapped around the axle with using "truthiness" for parameter validation so I left that sleeping demon alone.
var arraysMatch = function (arr1, arr2) {
// Check if the arrays are the same length
if (arr1.length !== arr2.length) return false;
// Check if all items exist and are in the same order
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
// Otherwise, return true
return true;
};
What is the best data type to store unique values only?
an array can have duplicates
[one, one, two]
and an object (? maybe wrong terminology) have unnecessary values for my current case
{one: something, two: something, three: something}
Shortly, I need something like this:
{one, two, three}
I am not sure what it is called, or if it does exist in js. Needing some enlightment.
You mean a structure called Set, and in the current version of ECMAScript there's no such structure. It will be standarized in the next version, however it's available now in some browsers.
You can emulate set using object, but as you said that also involves unnecessary values. If you don't want to care about them, you can use a library that emulates Set, like http://collectionsjs.com/
The most common way to solve this is to use an array, and just check if it already has the value you want to insert, that way it contains only unique values.
if ( arr.indexOf(value) == -1 ) arr.push(value);
In addition to obvious ways you can always create you own data structure on top of array if you need some more advanced functionality. For example:
function UArray(val) {
this._values = [];
if (typeof val !== 'undefined') {
this.set(val);
}
}
UArray.prototype.set = function(values) {
this._values = this._values.concat(values).filter(function(el, i, arr) {
return arr.indexOf(el) === i;
});
};
UArray.prototype.get = function() {
return this._values;
}
var uarr = new UArray();
uarr.set(['one', 'one', 'two']);
alert( uarr.get() );
uarr.set('two');
uarr.set(['three', 'one']);
alert( uarr.get() );
Such a custom data structure could be extended with additional necessary methods, i.e.:
remove to remove specific item
find to find item's index or -1 if not found,
etc.
I have feeling this must be a duplicate, but I've been unable to find anything, probably due to different wording, or just because there really is nothing better.
I am generating kind of huge chunk of JS code, which "ORs" object properties with variables, while identifiers don't necessarily match. It looks like this (values are boolean):
a.borderline = a.borderline || borderline;
a.st1 = a.st1 || st;
a.ref64 = a.ref64 || ref;
a.unfortunatelySometimesQuiteLongIndentifier123 = a.unfortunatelySometimesQuiteLongIndentifier123 || unfortunatelySometimesQuiteLongIndentifier;
...
To make it leaner I tried something like
a.st1 |= st;
but it makes a.st1 integer instead of boolean and I don't want to add another line with double negation to retype it back to boolean.
Using intuition I also tried ||=, which did not help :)
Is there any better (shorter) way of writing these commands?
Note: I cannot process the commands using a loop, because the commands are not executed all at once, instead they are spread in small chunks in the rest of the code (which was omitted for simplicity).
No, there is no shorthand OR operator in javascript. Coffeescript however does provide ||= and ?= to support this idiom.
Is there any better (shorter) way of writing these commands?
In your case, you're amending the a object instead of assigning to variables. You might do this in a loop fashion:
function amendWith(target, source)
for (var p in source)
if (!target[p])
target[p] = source[p];
return target;
}
amendWith(a, {
borderline: borderline,
st1: st,
ref64: ref,
unfortunatelySometimesQuiteLongIndentifier123: unfortunatelySometimesQuiteLongIndentifier
…
});
I'm not sure this is any shorter, but just as an alternative idea you could put the OR logic in a function and then loop through your values.
function myFunctionName(value1, value2) {
return value1 || value2;
}
//names are property names of object 'a' that you want to set, values are the alternate (default) values
var myMapping = {borderline:borderline, st1:st, reallyLongName123:reallyLongName};
for (temp in myMapping) {
a.temp = myFunctionName(a[temp], myMapping[temp]);
}
Since your unable to use a loop and you don't know all the values ahead of time, you could try adding the function to your object 'a'
a.test = function(propName, otherValue) {
this[propName] = this[propName] || otherValue;
};
a.test("borderline", borderline);