I am trying to loop through an argument that is passed to a method and I am getting a TypeError: individualExpenses.map is not a function. What am I doing wrong here?
class ExpenseTracker {
constructor(payCheck, monthlyExpenses) {
this.payCheck = payCheck;
this.monthlyExpenses = monthlyExpenses;
}
storeExpenses(individualExpenses) {
let expenseStore = [];
individualExpenses.map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
}
const v = new ExpenseTracker({}, {});
v.storeExpenses(1)
You are passing a numerical value to storeExpenses function and applying map over it. map works only on arrays. If you do
v.storeExpenses([1]);
it'll work just fine.
Alternatively, you can build logic to convert a non-array type to an array and use it in your storeExpenses function. This way you can do either of v.storeExpenses(1) or v.storeExpenses([1]) and the function will still work.
e.g.
const wrapToArray = (obj) => {
if (!obj) return [];
return Array.isArray(obj) ? obj : [obj];
};
and then modify your storeExpenses method as below -
storeExpenses(individualExpenses) {
let expenseStore = [];
wrapToArray(individualExpenses).map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
Related
I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}
I want to add a side effect every when an array being pushed. For example, I want to add console.log:
var arr = [];
arr.push(1); // => I want it to work normally, and in addition, it logs 1 to the console
How to achieve that? I'm looking for a solution using Proxy and I have tried handler.get() and handler.apply() but still, can't figure it out.
To directly answer your initial question...you need to return a closure from the get trap. To actually trap this, you would need to use proxy.push() instead of array.push(), though. For example:
const arr = [];
const arrProxy = new Proxy(arr, {
get(target, prop) {
if (prop === 'push') {
return (...args) => {
console.log(...args);
return target[prop](...args);
};
}
return target[prop];
}
});
arrProxy.push('test');
arrProxy.push('test1', 'test2');
Here's the final answer that I'm comfortable with, it doesn't use Proxy by the way.
{
var arr = [];
// add push
arr.push = function (...items) {
console.log(...items);
Array.prototype.push.apply(this, items);
};
arr.push('test');
arr.push('test1');
// clean up the push
delete arr.push;
}
something like that ?
Object.defineProperty(Array.prototype, 'myPush',
{
value : function (...val)
{
console.log(...val)
return this.push(...val)
}
})
let aa = []
aa.myPush( 5,123)
console.log('aa = ', aa )
The code below works for me
Promise.all([first, second, third]).then([first, second, third] => {
console.log(second);
});
I know that console.log(second) will give me the value with the key second.
My promises are dynamically set and now it looks like below:
let collection = [second, third];
Promise.all(collection).then((collection) => {
console.log(collection);
});
In this example I set two values in collection. In real life it can include more or less values.
When I use console.log(collection) it will output collection[0] and collection[1]. In this case I don't know what which value collection[1] is.
Question
How can I, like my first example, have something like named dynamically arguments like collection['second'] or similar?
As we want to access the value dynamically, set collection to an empty object first. Then, use the keys from collection to pass all its Promise-values to Promise.all. Then, map back the fulfilled values and then, we can access collection's value by some key.
let collection = {}
for (let i = 0; i < 3; i++) {
collection[`key${i}`] = Promise.resolve(i)
}
let collectionKeys = Object.keys(collection)
Promise.all(collectionKeys.map(key => collection[key]))
.then(values => {
let collectionFulfilled = collectionKeys.reduce((obj, key, i) => {
obj[key] = values[i]
return obj
}, {})
console.log(collectionFulfilled)
})
If you pass your promises embedded inside an object with a single key, you could use that for it's name, and then with a simple helper function reverse the values & keys from this.
With the new ES6 you can then just pass like -> [{one}, {two}, {three}] etc.
Below is an example with a helper function called namedPromiseAll.
function namedPromiseAll(named) {
const pcollection =
named.map(m => Object.values(m)[0]);
const ncollection =
named.map(m => Object.keys(m)[0]);
return Promise.all(pcollection).then((c) => {
return c.reduce((a,v,ix) => {
a[ncollection[ix]] = v;
return a;
}, {});
});
}
const second = Promise.resolve(2);
const third = Promise.resolve(3);
const collection = [{second}, {third}];
namedPromiseAll(collection).then(console.log);
I am trying to re-write a function that filters out a specific property of an object to a function that can be passed a property and filter it.
This is the initial function:
function filterCategory(xmlObject, id) {
let newData = [];
xmlObject
.Sports[0]
.Sport[0]
.Category
.map(function (category) {
if (category.$.CategoryID == id) {
newData.push(category);
}
});
xmlObject
.Sports[0]
.Sport[0]
.Category = newData;
return xmlObject;
}
This is my new function:
function filterProperty(xmlObject, property, idtype, id) {
let newData = [];
if(xmlObject.hasOwnProperty(property)) {
xmlObject.property.map(function(value) {
if(value.$.idtype == id) {
newData.push(value);
}
});
xmlObject.property = newData;
}
return xmlObject;
}
For the second function my linter returns Unused idtype. Will my function be able to access the argument, or will it fail because I am trying to call it from a map() function? If so, how can I avoid this?
If you want to use idtype as a dynamic object property, then you can't use it like my.object.idtype as that will look for the property on the object that is literally called "idtype", instead you can use bracket notation to access the property
value.$[idtype];
Further illustration:
var obj = { one: 1, two: 2, three: 'foobarbaz' };
function getThingFromObject(mything) {
return obj[mything];
}
console.log(getThingFromObject('one')); // 1
console.log(getThingFromObject('three')); // 'foobarbaz'
So, I need to transform all keys in array from underscore to camel space in js. That is what I need to do before send form to server. I'm using Angular.js and I want to represent it as a filter (but I think it's not rly important in this case). Anyway, here is a function I've created.
.filter('underscoreToCamelKeys', function () {
return function (data) {
var tmp = [];
function keyReverse(array) {
angular.forEach(array, function (value, key) {
tmp[value] = underscoreToCamelcase(key);
});
return tmp;
}
var new_obj = {};
for (var prop in keyReverse(data)) {
if(tmp.hasOwnProperty(prop)) {
new_obj[tmp[prop]] = prop;
}
}
return new_obj;
};
function underscoreToCamelcase (string) {
return string.replace(/(\_\w)/g, function(m){
return m[1].toUpperCase();
});
}
})
Here I will try to explain how it works, because it looks terrible at first.
underscoreToCamelcase function just reverting any string in underscore to came case, except first character (like this some_string => someString)
So, as I said earlier, I should revert all keys to camel case, but as you understant we can't simply write
date[key] = underscoreToCamelcase(key)
so keyReverse function returns a reverted array, here is example
some_key => value
will be
value => someKey
and for the last I simply reverting keys and values back, to get this
someKey => value
But, as you already may understand, I got a problem, if in array exists the same values, those data will be dissapear
array
some_key1 => value,
some_key2 => value
returns as
someKey2 => value
So how can I fix that? I have a suggestion to check if those value exists and if it is add some special substring, like this
some_key1 => value,
some_key2 => value
value => someKey1,
zx99value => someKey2
and after all parse it for zx99, but it I think I`m going crazy...
Maybe some one have a better solution in this case?
Important! Them main problem is not just to transform some string to camel case, but do it with array keys!
If you use an existing library to do the camelCase transform, you can then reduce an object like so
import {camelCase} from 'lodash/string'
const camelCaseKeys = (obj) =>
Object.keys(obj).reduce((ccObj, field) => ({
...ccObj,
[camelCase(field)]: obj[field]
}), {})
.filter('underscoreToCamelKeys', function () {
return function (data) {
var tmp = {};
angular.forEach(data, function (value, key) {
var tmpvalue = underscoreToCamelcase(key);
tmp[tmpvalue] = value;
});
return tmp;
};
function underscoreToCamelcase (string) {
return string.replace(/(\_\w)/g, function(m){
return m[1].toUpperCase();
});
}
})
thanks to ryanlutgen
As an alternative solution, you could use the optional replacer parameter of the JSON.stringify method.
var result = JSON.stringify(myVal, function (key, value) {
if (value && typeof value === 'object') {
var replacement = {};
for (var k in value) {
if (Object.hasOwnProperty.call(value, k)) {
replacement[underscoreToCamelcase(k)] = value[k];
}
}
return replacement;
}
return value;
});
Of course you'll end up with a string and have to call JSON.parse to get the object.