Destructuring first value of an array of objects in reduce method - javascript

Let's say you an array of objects, where you're reducing a property into one result separated by dashes, e.g:
const array = [
{ foo: "foo" },
{ foo: "foo" },
]
Should become the string:
foo-foo
If you're using the reduce method, you might do something like this:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
return accumulator + "-" + foo;
} );
console.log( result );
However, the problem is that the default initial value (the first element of the array) is the entire first object, naturally resulting in [object Object]-foo-foo.
Question is: is there a simple way to, for example destructure, the initial value?
You can bypass the issue by, for example, using an if-statement specifically checking whether the accumulator is currently an object:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
if ( accumulator.hasOwnProperty( "foo" ) ) {
return accumulator.foo + "-" + foo;
}
return accumulator + "-" + foo;
} );
console.log( result );
However, I am interested in a simpler/prettier way of doing this, using less "arbitrary-looking" code, perhaps a way to do this using the actual initialValue argument of reduce.
Note: I am not looking for an answer to an actual real problem I am facing now (and thus not looking for alternative solutions such as for let/of loops or filtering the array), I am asking for the sake of learning more about the reduce method in these types of situations for future reference.

You could set the initial value to an empty string, and use a ternary to check if the string is empty, if true, then return only the foo, otherwise, return the accumulator, a dash, and foo:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce((a, {foo}) => a ? `${a}-${foo}` : foo, '');
console.log(result);
We can be sure that on the initial loop that the ternary will return only foo as '' evaluates to false.

You could map and join the items.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array
.map(({ foo }) => foo)
.join('-');
console.log(result);
A reduce approach with a check if a dash is necessary.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array.reduce((r, { foo }) => r + (r && '-') + foo, '');
console.log(result);

Related

How to use variable value directly in a dynamically created JavaScript getter?

I have the following code for adding getters to an object on the fly:
const obj = {};
const uni = { name1: ..., name2: ..., ...};
for(const nm in uni) {
const u = obj[nm] = {};
Object.defineProperty(u, 'value', {
configurable: true,
enumerable: true,
get: () => { return uni[nm] },
});
}
I want getters to return different values, so I want uni[nm] to be understood literary, i.e. the first getter would return uni.name1, the second uni.name2 etc.
How do I do that?
EDIT:
I want to dynamically create an object like this:
{
name1: { get value() { return uni.name1 }},
name2: { get value() { return uni.name2 }},
...
}
Don't use for..in. And you pretty much never need Object.defineProperty. That's all stone age JavaScript.
Instead use -
for..of to iterate over Object.keys.
get keyword to define a getter
const uni = { name1: "foo", name2: "bar" }
let obj = {}
for(const key of Object.keys(uni)) {
obj[key] = {
get value() {
return uni[key]
}
};
}
console.log(obj.name1.value) // "foo"
console.log(obj.name2.value) // "bar"
uni.name1 = "zzz"
console.log(obj.name1.value) // "zzz"
This can be expressed using array.reduce and -
object spread to extend the definition of an object
computed properties to define a dynamic key -
const uni = { name1: "foo", name2: "bar" }
const obj = Object.keys(uni).reduce(
(o, key) => {
return {...o, [key]: { get value() { return uni[key] }}} // extend
},
{} // initial object
)
console.log(obj.name1.value) // "foo"
console.log(obj.name2.value) // "bar"
uni.name1 = "zzz"
console.log(obj.name1.value) // "zzz"
You can achieve this by using the square bracket notation ([]) to dynamically access properties in the uni object, like this:

Undefined vs Empty String - Javascript with example

I am trying to find the first processed === true and return the value.
My works seems to work but if there are no processed the code turns undefined instead of <empty string>
const values = [{
value: "bar",
process: false
},
{
value: "foo",
process: false
}
];
const myValue = values.reduce((acc, curr) => {
if (curr.primary) {
return curr.value.toLowerCase();
}
}, '');
console.log("My Value: " + myValue);
What am I missing here?
reduce calls the callback function once for each item in the loop.
curr is the current value. acc is the return value from the previous function (or the second argument ('') the first time going around the loop.
Your code completely ignores acc so the value returned by reduce depends entirely on the last value in the array.
Your function tests that value with curr.primary. If that is a true value it return curr.value.toLowerCase(). If it isn't a true value, then it gets to the end of the function without hitting a return statement. Any function that doesn't return will return undefined.
To find the first value that matches a condition, use find not reduce.
Then do a test to use an empty string if you didn't find anything.
const match = values.find(value => value.primary === true);
const myValue = match?.value.toLowerCase() ?? "";
I am trying to find the first processed === true and return the value [else an empty string ""].
You can use Array.prototype.find() with the optional chaining operator (?.) along with the nullish coalescing operator (??) in the case that no value is found:
const array1 = [
{ value: 'bar', processed: false },
{ value: 'foo', processed: false },
];
const myValue1 = array1.find(o => o.processed)?.value.toLowerCase() ?? '';
console.log('myValue1:', myValue1); // "myValue1:" ""
console.log('type:', typeof myValue1); // "type:" "string"
const array2 = [
{ value: 'BAR', processed: false },
{ value: 'FOO', processed: true },
];
const myValue2 = array2.find(o => o.processed)?.value.toLowerCase() ?? '';
console.log('myValue2:', myValue2); // "myValue2:" "foo"
console.log('type:', typeof myValue2); // "type:" "string"
Here's a breakdown of the syntax evaluation for the first value:
const myValue1 = array1.find(o => o.processed)?.value.toLowerCase() ?? '';
// 11111111111111111111111111111 22222222222222222222 44 55
// 33333333333333333333333333333333333333333333333333
// 1. This evaluates to undefined
// 2. So this part is not evaluated, and...
// 3. the entire left side evaluates to undefined
// 4. Because 3 is nullable (null or undefined), when the ?? operator executes...
// 5. the left side (3) is ignored, and the right side (5) becomes the value
In contrast with the value computed from the second array:
const myValue2 = array2.find(o => o.processed)?.value.toLowerCase() ?? '';
// 11111111111111111111111111111 22222233333333333333 55 66
// 44444444444444444444444444444444444444444444444444
// 1. This evaluates to { value: 'FOO', processed: true }
// 2. So this part is evaluated to "FOO"
// 3. which becomes "foo", and...
// 4. the entire left side evaluates to "foo"
// 5. Because 4 is NOT nullable (null or undefined), when the ?? operator executes...
// 6. the left side (4) becomes the final value, and the right side (6) is ignored
To find the first element that meets a condition you should use find method.
Reduce will run on the entire array even after that condition is met since it's purpose is to reduce an entire array into a different data structure (i.e string/object). Find method will exit as soon as an item that meets the condition is found (or after checking the entire array if non of the items does)
As to replacing undefined with an empty string, in the attached snippet I used a or condition (although some would prefer conditional operator), since undefined is falsey, it will insert the empty string.
const values = [{
value: "bar",
process: false
},
{
value: "foo",
process: false
}
];
const myValue = values.find(curr => curr.process);
console.log("My Value: " + (myValue?.value?.toLowerCase() || ''));
Best of luck with your project:)
This should be all:
const values = [{
value: "bar",
process: false
},
{
value: "foo",
process: false
}
];
const myValue = values.reduce((acc, curr) => curr.process ? curr.value.toLowerCase() : '', '');
console.log("My Value: " + myValue);
you might be better off using a foreach loop to accomplish what you are trying to do:
const values = [{
value: "bar",
process: false
},
{
value: "foo",
process: false
}
];
var processedValue = '';
values.forEach(element => {
if(element.process && !processedValue)
processedValue = element.value;
});
console.log(processedValue);

Aggregate same key values into an array and avoid undefined

I am trying to aggregate the same key values into an array by value.
so for example I have an array of objects, like so
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}]
when they get aggregated the array transforms into
[
foo: {true: [{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: [{buzz: false}]}
]
the array entries is the original object.
Now I know the keys that I want to group by..
they are foo, bar, buzz and fizz.
But fizz is not part of the original array, so the return is undefined, like so
[
foo: {true:[{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: A[{buzz: false}]}
fizz: {undefined: [{foo: true},{foo: false},{bar: true},{buzz: false}]}
],
how do I reduce the original array without including the fizz value that is undefined?
code here:
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ]
for (let x = 0; x < types.length; x++) {
let data = data.reduce((acc, i) => {
if (!acc[i[types[x]]]) {
acc[i[types[x]]] = [i]
}
else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
}
else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {})
v.push({ [types[x]]: data });
}
return v;
You were close, you just need to check if the property you were adding was undefined before adding. You can also check if the reduced object has any properties before adding to the result object.
Note that this may not be the most efficient way of doing it, but sometimes it's better to understand the code than it is to have highly efficient code.
const data = [{
foo: true
}, {
foo: false
}, {
bar: true
}, {
buzz: false
}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz']
for (let x = 0; x < types.length; x++) {
let reduced = data.reduce((acc, i) => {
// /* Added this type check */
if (!acc[i[types[x]]] && typeof i[types[x]] !== 'undefined') {
acc[i[types[x]]] = [i]
} else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
} else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {});
// Doesn't add a property for the type if there are no data
if (Object.keys(reduced).length) {
v.push({
[types[x]]: reduced
});
}
}
console.log(v);
Have a look at how Array.prototype.reduce works. It might be the right method to build your approach upon.
A generic way of solving the OP's problem was to iterate the provided data array. For each item one would extract its key and value. In case the item's key is listed (included) in another provided types array, one would continue creating a new data structure and collecting the currently processed item within the latter.
One does not want to iterate the types array for it will cause a unnecessarily complex lookup for the data items, each time a type item is going to be processed.
Thus a generically working (better code reuse) reduce method might be the best solution to the OP's problem ...
const sampleDataList = [
{ foo: true },
{ foo: false },
{ bar: true },
{ baz: false },
{ buzz: false },
{ baz: false },
{ bar: true }
];
// foo: {true: [{foo: true}], false: [{foo: false}]},
// bar: {true: [{bar: true}]},
// buzz: {false: [{buzz: false}]}
function collectItemIntoInclusiveKeyValueGroup(collector, item) {
const { inclusiveKeyList, index } = collector;
const firstItemEntry = Object.entries(item)[0];
const key = firstItemEntry[0];
const isProceedCollecting = ( // proceed with collecting ...
//
!Array.isArray(inclusiveKeyList) // - either for no given list
|| inclusiveKeyList.includes(key) // - or if item key is listed.
);
if (isProceedCollecting) {
let keyGroup = index[key]; // access the group identified
if (!keyGroup) { // by an item's key, ... or ...
// ...create it in case ...
keyGroup = index[key] = {}; // ...it did not yet exist.
}
const valueLabel = String(firstItemEntry[1]); // item value as key.
let valueGroupList = keyGroup[valueLabel]; // acces the group list
if (!valueGroupList) { // identified by an item's
// value, ...or create it in
valueGroupList = keyGroup[valueLabel] = []; // case it did not yet exist.
}
// push original reference into a grouped
// key value list, as required by the OP.
valueGroupList.push(item);
}
return collector;
}
console.log(
"'foo', 'bar', 'buzz' and 'fizz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'buzz', 'fizz'],
index: {}
}).index
);
console.log(
"'foo', 'bar' and 'baz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'baz'],
index: {}
}).index
);
console.log(
"all available keys :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
index: {}
}).index
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Try something like:
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ];
for (let x = 0; x < types.length; x++) {
let filteredlist = data.filter(function (d) {
return Object.keys(d)[0] == types[x];
});
let isTrue = 0;
let isFalse = 0;
if (filteredlist.length > 0) {
for (let i = 0; i < filteredlist.length; i++) {
let trueOrfalse = eval("filteredlist[i]." + types[x]);
if (trueOrfalse) {
isTrue++;
} else {
isFalse++;
}
}
v.push(types[x], {true: isTrue, false: isFalse});
}
}
console.log(v);
Assuming you only want to count the number of each key (e.g. true or false) you can use the following code.
I've written this as a function named 'aggregate' so that it can be called multiple times with different arguments.
const initialData = [{foo: true},{foo: true},{foo: false},{bar: true},{buzz: false}];
const types = ['foo', 'bar', 'buzz', 'fizz'];
const aggregate = (data, types) => {
const result = {};
data.forEach(item => {
// Extract key & value from object
// Note: use index 0 because each object in your example only has a single key
const [key, value] = Object.entries(item)[0];
// Check if result already contains this key
if (result[key]) {
if (result[key][value]) {
// If value already exists, append one
result[key][value]++;
} else {
// Create new key and instantiate with value 1
result[key][value] = 1;
}
} else {
// If result doesn't contain key, instantiate with value 1
result[key] = { [value]: 1 };
}
});
return result;
};
console.log(aggregate(initialData, types));
This will output the following (note I've added another {foo: true} to your initialData array for testing).
The output should also be an object (not array) so that each key directly relates to its corresponding value, as opposed to an Array which will simply place the value as the next item in the Array (without explicitly linking the two).
{
foo: { true: 2, false: 1 },
bar: { true: 1 },
buzz: { false: 1 }
}

How can I compile and evaluate logical operators and operands specified on an object?

Is there a compiler or an easy way I can compile and evaluate logical operators and operands specified on an object. This is to akin to mongodb $or and $and operators. For example:
return {
$or: [
foo: [...],
bar: [...]
]
}
When the compiler encounters foo it will call a corresponding function with the value provided for the same. The same goes for bar. It will then logical OR the results of the two operations. I want to handle $and and $or operators. I would do simple checks for such a simple example but I want to have the ability to nest the logical operators. A complex example:
return {
$or: [
{
$and: [
{ foo: [...] },
{ bar: [...] }
]
},
{ baz: [...] },
() => m < n
]
}
Simplified definition of foo, bar and baz:
export const evalFoo = items => {
return items.indexOf("foo") >= 0;
};
export const evalBar = items => {
return items.indexOf("bar") >= 0;
};
export const evalBaz = items => {
return items.indexOf("baz") >= 0;
};
Sample data:
Set 1
m = 4; n = 1; foo: ['foo', 'x']; bar: ['bar', 'y']; baz: ['baz', 'z']
RESULT = true; // because $and results to true.
Set 2
m = 4; n = 1; foo: ['x']; bar: ['y']; baz: ['x']
RESULT = false; // because m > n and $and results to false.
Set 3
m = 1; n = 3; foo: ['x']; bar: ['y']; baz: ['x']
RESULT = true; // because m < n.
Set 4
m = 3; n = 1; foo: ['x']; bar: ['bar']; baz: ['z']
RESULT = true; // because m > n, baz() is false and x and $and is false.
You could take something like this, where you differentiate between $and and $or or the functions.
It works by taking an object with the keys for array methods like Array#every, which acts like a logical and by testing the values in an object and return true if all items with their callbacks return a truthy value. Analogous works Array#some, but there is only one item necessary which callback returns a truthy value.
The other object contains functions and allows to access them by using the key.
The first par checks if the parameter is a function and if so, it returns the result of the call.
Then the parameter gets a check and if falsy, like null or if the value is not an object, the function terminates with false.
For taking a key/value pair a destructuring assignment takes place with the first entry from the object.
If key is in the operator object, the value is taken as method for iterating the value and returned.
If key is in the functions object, then the function is called with value as parameter and returned.
Finally a false is returned, because no other check was true and the condition can not be resolved.
function evaluate(object) {
var operators = { $or: 'some', $and: 'every' },
fns = {
foo: items => items.indexOf("foo") >= 0,
bar: items => items.indexOf("bar") >= 0,
baz: items => items.indexOf("baz") >= 0
},
key,
value;
if (typeof object === 'function') return object();
if (!object || typeof object !== 'object') return false;
[key, value] = Object.entries(object)[0];
if (key in operators) return value[operators[key]](evaluate);
if (key in fns) return fns[key](value);
return false;
}
var m = 4,
n = 1,
object = {
$or: [
{
$and: [
{ foo: ['foo', 'x'] },
{ bar: ['bar', 'y'] }
]
},
{ baz: ['baz', 'z'] },
() => m < n
]
},
result = evaluate(object);
console.log(result);

How to update react state properly

I am new to react and I want to ask what's the best way to update state, I have some code. I know the code below is not correct as it's setting the state directly.
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index].bar = !foo.bar;
this.setState({ foos });
};
Those two code below which one is better? can some one explain me please!
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index] = { ...foo };
foos[index].bar = !foo.bar;
this.setState({ foos });
};
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index] = { ...foos[index] };
foos[index].bar = !foos[index].bar;
this.setState({ foos });
};
My account got blocked by some down votes questions, the funny thing is I have to re-edit them, even though I already have the accepted answer.I do not understand what's the point to do this.I am so frustrated by this stackoverflow system.
Now, I basically can do nothing but keep editing my questions, and they have all been answered. This is ridiculous !!!
You should use Array.prototype.map() method, like this:
handlexxx = foo => {
const foos = this.state.foos.map(f => {
if(foo.id === f.id) return {...f, bar: !f.bar}; // assume that the element has an identifier id
return f;
})
this.setState({ foos });
};
For short, using ternary operator instead of if-else statement
handlexxx = foo => {
const foos = this.state.foos.map(f => foo.id === f.id ? {...f, bar: !f.bar} : f
this.setState({ foos });
};
One classic way to avoid mutations even for complex nested objects is to use JSON.parse(JSON.stringify(COMPLICATED_OBJECT)). This will return a representation of your object that has no reference to the original object, so you can mutate the copy without affecting the original:
var foos = [
{ id: 1, bar: false },
{ id: 2, bar: false },
{ id: 3, bar: false },
]
var foo = foos[0];
var _foos = JSON.parse(JSON.stringify(foos)).map(f => {
if (f.id === foo.id) f.bar = !foo.bar;
return f;
});
If you run this, you'll see foos is unchanged but _foos is updated
At the end of the day, you might want to think about which solution you find most readable, and which solution other developers on your team might find most readable. If you have to return to this code in 3 years, you'll want to be able to read the code off the page without any head scratching.
Since your foo object has an id property it is better to use .map method and mix this with spread syntax or Object.assign, then return the related elements as #Nguyễn Thanh Tú explained. But, if you want to do this with indexes here are the examples.
You can use findIndex, not indexOf. Since indexOf just look for a value, findIndex accepts a function. Find the index of the item, map the array, then if index matches do the change if it does not match return the unrelated item.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState = foo => {
const index = state.foos.findIndex(el => el.id === foo.id );
const newFoos = state.foos.map( ( foo, i ) => {
if ( i !== index ) { return foo };
return { ...foo, bar: !foo.bar };
})
console.log( newFoos );
}
handleState(foo);
Second one. Here, we are using Object.assign in a tricky way. Instead of mapping the array we use Object.assign and change the item using its index.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState2 = foo => {
const index = state.foos.findIndex(el => el.id === foo.id );
const newFoos = Object.assign( [], state.foos, { [index]: { ...foo, bar: !foo.bar } });
console.log( newFoos );
}
handleState2(foo);
Third one. Without an index, with only .map and using directly the id property. We don't need to find an index here, we are just checking with the id property to find the right item.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState3 = foo => {
const newFoos = state.foos.map( el => {
if ( el.id !== foo.id ) { return el };
return { ...el, bar: !el.bar };
})
console.log( newFoos );
}
handleState3( foo );

Categories