NodeJs - optimization multiple if - javascript

Here is my code :
Search.prototype.makeQuery = function (data) {
let result = {};
if (data.orderId) {
result["order_id"] = data.orderId;
}
if (data.userMobileNumber) {
result["user.Number"] = {$regex : data.userMobileNumber}
}
if (data.userFullName) {
result["user.Name"] = {$regex: data.userFullName}
}
return result;
};
All I want is finding better way to optimize my code and reduce if condition in my code. Is there any suggestion ?

You can avoid the typing of if when you wrap it into a function and the typing of data with destructuring.
The advantage of wrapping the if in this case into a function is that you can simply test it, it is reusable and easy to read
Code
Search.prototype.makeQuery = function (data) {
let result = {}
let {orderId, userMobileNumber, userFullName} = data
setObjectValue(orderId, result, "order_id", orderId)
setObjectValue(userMobileNumber, result, "user.Number", {$regex : userMobileNumber})
setObjectValue(userFullName, result, "user.Name", {$regex: userFullName})
return result;
}
function setObjectValue(condition, object, key, value) {
if(condition) {
object[key] = value
}
}
Working Example
function makeQuery (data) {
let result = {}
let {orderId, userMobileNumber, userFullName} = data
setObjectValue(orderId, result, "order_id", orderId)
setObjectValue(userMobileNumber, result, "user.Number", {$regex : userMobileNumber})
setObjectValue(userFullName, result, "user.Name", {$regex: userFullName})
return result;
}
function setObjectValue(condition, object, key, value) {
if(condition) {
object[key] = value
}
}
let data = {
orderId: 1,
userMobileNumber: "016875447895",
userFullName: "John Doe"
}
let query = makeQuery(data)
console.log(query)

A simpler way:
Search.prototype.makeQuery = function (data) {
let result = {};
data.orderId && (result["order_id"] = data.orderId);
data.userMobileNumber && (result["user.Number"] = {$regex : data.userMobileNumber});
data.userFullName && (result["user.Name"] = {$regex: data.userFullName});
return result;
};

Let's imagine you have many fields or you want to modify them, you would create a map. Right now, your code works and my solution is overkill but it may be useful in the future:
const interestingData = new Map()
//I tried to imitate your values.
// Use whatever function you want here as a callback.
// value is the value you request, the callback must return the value you want to set.
interestingData.set("order_id", value => value)
interestingData.set("user.Number", value => ({ $regevalue: value }))
interestingData.set("user.Name", value => ({ $regevalue: value }))
//Tgis is a Factory in case you need several search.
const makeSearch = fields => data => {
let result = {}
fields.forEach((callBack, field) => {
if (data[field])
result[field] = callBack(data[field])
})
return result
}
//Creating a searching function
const myResearch = makeSearch(interestingData)
//Fake examples
const data1 = {
order_id: 'ertyui',
"user.Number": "ertyuio",
"user.Name": "ertyuio",
azerr: 123456
}
const data2 = {
order_id: 'ertyui',
"user.Number": "ertyuio",
}
console.log(myResearch(data1))
console.log(myResearch(data2))
It is not simpler but it is more extensible, and when you have many parameters, it is going to be much faster on a big scale. It is also reusable. Hope that helps!

Not sure if you'd consider this as code optimization, but you can get rid of the if statements using Object.assign:
Search.prototype.makeQuery = function (data) {
return Object.assign({},
data.orderId && { order_id: data.orderId },
data.userMobileNumber && {
'user.Number': { $regex : data.userMobileNumber },
},
data.userFullName && {
'user.Name': { $regex : data.userFullName },
},
)
};
If you can use newer JS features (with a transpiler or otherwise), you could use Object rest/spread for a slightly more concise syntax:
Search.prototype.makeQuery = (data) => ({
...data.orderId && { order_id: data.orderId },
...data.userMobileNumber && {
'user.Number': { $regex : data.userMobileNumber },
},
...data.userFullName && {
user.Name': { $regex : data.userFullName },
},
});
Edit 1: note that all these are pure functions, no mutations are taking place whatsoever

Related

Using "modern" javascript to get deeply nested object

I'm trying to work around the fact that Datocms doesn't support a where filter in their GraphQL schema. Since there isn't that much data, I figured I could query all of it, and do the find on my end, but ... I'm not succeeding, at least not using "modern" methods.
What I get back when I query all of the data looks like this:
"foo": {
"data": {
"allGiveawayLandingPages": [
{
"lpSection": [
{},
{},
{},
{},
{},
{},
{},
{
"id": "34525949",
"products": [
{
"__typename": "PurchaseCardRecord",
"discountAmount": 50,
"discountAmountPct": null,
"discountEndDate": "2022-11-01T23:00:00+00:00",
"id": "44144096"
},
{
"__typename": "PurchaseCardRecord",
"discountAmount": null,
"discountAmountPct": null,
"discountEndDate": null,
"id": "44144097"
}
]
}
]
}
]
}
}
I need to find the object down in the "products" array by "id". This general question has been asked and answered lots of times, but the only answer I can get to work is from way back in 2013, and it seems to me there aught to be a more modern way to do it.
I'm doing this inside of a try/catch block, which I mention because it seems to be making this hard to debug (I'll come back to this):
export default async function createPaymentIntentHandler(req, res) {
const body = JSON.parse(req.body);
const {
productId,
productType
} = body;
let data;
if ('POST' === req.method) {
try {
switch (productType) {
case 'SeminarRecord':
data = await request({ query: singleSeminarQuery(productId) });
productObjName = 'seminar';
break;
default:
data = await request({ query: singleProductQuery(productId) });
productObjName = 'product';
}
/**
* Here's where I want to do my query / filtering
*/
// ... do more stuff and create Stripe paymentIntent
res.status(200).send({clientSecret: paymentIntent.client_secret})
} catch (error) {
logger.error({error}, 'Create Payment Intent error');
return res.status(400).end(`Create Payment Intent error: ${error.message}`);
}
} else {
res.status(405).end('Method not allowed');
}
}
My first, naive attempt was
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = foo.data.allGiveawayLandingPages.find((page) => {
return page.lpSection.find((section) => {
return section?.products.find((record) => record.id === parentId)
})
});
logger.debug({card}, 'Got card');
In the abstract, aside from the fact that the above is fairly brittle because it relies on the schema not changing, I'd expect some similar sort of ES6 construction to work. This particular one, however, throws, but not in a particularly useful way:
[08:09:18.690] ERROR: Create Payment Intent error
env: "development"
error: {}
That's what I meant by it being hard to debug — I don't know why the error object is empty. But, in any case, that's when I started searching StackOverflow. The first answer which looked promising was this one, which I implemented as
...
const {
productId,
productType,
parentId
} = body;
...
function findCard(parent, id) {
logger.debug({parent}, 'searching in parent')
for (const item of parent) {
if ('PurchaseCardRecord' === item.__typename && item.id === id) return item;
if (item.children?.length) {
const innerResult = findCard(item.children, id);
if (innerResult) return innerResult;
}
}
}
if ('POST' === req.method) {
try {
...
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = findCard(foo, parentId);
logger.debug({card}, 'Got card');
This similarly throws unhelpfully, but my guess is it doesn't work because in the structure, not all children are iterables. Then I found this answer, which uses reduce instead of my original attempt at find, so I took a pass at it:
const card = foo.data.allGiveawayLandingPages.reduce((item) => {
item?.lpSection.reduce((section) => {
section?.products.reduce((record) => {
if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
})
})
})
This is actually the closest I've gotten using ES6 functionality. It doesn't throw an error; however, it's also not returning the matching child object, it's returning the first parent object that contains the match (i.e., it's returning the whole "lpSection" object). Also, it has the same brittleness problem of requiring knowledge of the schema. I'm relatively certain something like this is the right way to go, but I'm just not understanding his original construction:
arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
I've tried to understand the MDN documentation for Array.reduce, but, I don't know, I must be undercaffeinated or something. The syntax is described as
reduce((previousValue, currentValue) => { /* … */ } )
and then several variations on the theme. I thought it would return all the way up the stack in my construction, but it doesn't. I also tried
const card = foo.data.allGiveawayLandingPages.reduce((accumulator, item) => {
return item?.lpSection.reduce((section) => {
return section?.products.reduce((record) => {
if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
})
})
})
but the result was the same. Finally, not understanding what I'm doing, I went back to an older answer that doesn't use the ES6 methods but relies on recursing the object.
...
function filterCards(object) {
if (object.hasOwnProperty('__typename') && object.hasOwnProperty('id') && ('PurchaseCardRecord' === object.__typename && parentId === object.id)) return object;
for (let i=0; i<Object.keys(object).length; i++) {
if (typeof object[Object.keys(object)[i]] == 'object') {
const o = filterCards(object[Object.keys(object)[i]]);
if (o != null) return o;
}
}
return null;
}
if ('POST' === req.method) {
try {
...
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = filterCards(foo);
logger.debug({card}, 'Got card');
This actually works, but ISTM there should be a more elegant way to solve the problem with modern Javascript. I'm thinking it's some combination of .find, .some, and .reduce. Or maybe just for ... in.
I'll keep poking at this, but if anyone has an elegant/modern answer, I'd appreciate it!

How to insert key in object based on Id

I have an array of object, I want to add key in my specifi object of array when Id is matched. I have tried this:
this.data.forEach(value => {
if (value.Id === attachmentDataId) {
AttachmentTypeId: this.attachmentRecord.AttachmentType;
}
});
But it's not working and it's not giving any error also
Try this out :
let data = [{ id: 1 }, { id: 5 }];
const attachmentDataId = 5;
const attachmentRecord = { AttachmentType: "AttachmentType" };
data.forEach(value => {
if (value.id === attachmentDataId) {
value.AttachmentTypeId = attachmentRecord.AttachmentType;
}
});
The stackblitz example: https://stackblitz.com/edit/js-nrhouh
You could use the index parameter of forEach function to access the specific object of the array.
this.data.forEach((value, i) => {
if (value.Id === attachmentDataId) {
this.data[i] = {
...this.data[i],
AttachmentTypeId: this.attachmentRecord.AttachmentType
};
}
});
Inside the if block, you could also instead do
this.data[i]['AttachmentTypeId'] = this.attachmentRecord.AttachmentType;
I just find using the spread operator cleaner.
use javascript map() method.
Map() return a new array, it takes a callback, iterates on each element in that array
const updatedData = data.map(res => {
if(res.id === attachmentDataId) {
res.AttachmentTypeId = attachmentRecord.AttachmentType;
}
return res
})

How to solve "Expected to return a value in arrow function" error in eslint

I am using eslint and getting this error.
Expected to return a value in arrow function
The error is showing on the third line of the code.
useEffect(() => {
let initialPrices = {};
data.map(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
The map function must return a value. If you want to create a new object based on an array you should use the reduce function instead.
const reducer = (accumulator, { category, options }) => (
{...accumulator, [category]:options[0].price}
)
const modifiedData = data.reduce(reducer)
More information https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
The map function is intended to be used when you want to apply some function over every element of the calling array. I think here it's better to use a forEach:
useEffect(() => {
let initialPrices = {};
data.forEach(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
Your map function should return something. Here it's not the case so the error happens. Maybe a reduce function will be more appropriate than map?
From what I can see in your case, is that you want to populate initialPrices, and after that to pass it setSelectedPrice. The map method is not a solution, for you in this case, because this method returns an array.
A safe bet in your case would a for in loop, a forEach, or a reduce function.
const data = [
{
category: "ball",
options: [
{
price: "120.45"
}
]
},
{
category: "t-shirt",
options: [
{
price: "12.45"
}
]
}
];
The forEach example:
let initialPrices = {};
// category and options are destructured from the first parameter of the method
data.forEach(({ category, options}) => {
initialPrices[category] = options[0].price;
});
// in this process I'm using the Clojure concept to add dynamically the properties
setSelectedPrice(initialPrices);
The reduce example:
const initialPrices = Object.values(data).reduce((accumulatorObj, { category, options}) => {
accumulatorObj[category] = options[0].price
return accumulatorObj;
}, {});
setSelectedPrice(initialPrices);

how to replace modified object in array of objects using react js

i have object structure like this,
[{
"id":"8661c8c96df94ac78283360e0d1c86bd",
"modifiedObject":{....},
"originalObject":{...}
},
{
"id":"1525drd616dr17d78283360e0d1c86bd",
"modifiedObject":null,
"originalObject":{...}
},
{
"id":"6767srsr14542525276767cbd246464",
"modifiedObject":{....},
"originalObject":null
}]
I am finding an object with the id and get the inner object(if modified object present if not original object) and then modifying the data using below code
const originalCopyObject = projObjs.find(s => s.id === projectObjectId);
const targetCopyObject = originalCopyObject.modifiedObject || originalCopyObject.originalObject; // here in this case it always be one either modified or original object
const targetMutatedCopyObject = cloneDeep(targetCopyObject);
if (!targetMutatedCopyObject?.glazingOrGasMaterials.length) {
targetMutatedCopyObject.glazingOrGasMaterials = [
...targetMutatedCopyObject.glazingGasMaterials,
...targetMutatedCopyObject.glazingSimpleMaterials,
];
}
targetMutatedCopyObject.opaqueConstructions.forEach(transformConstructions);
Now targetMutatedCopyObject is having one of the modifiedObject or originalObject, How can I replace this targetMutatedCopyObject object in projObjs.
Could any one please let me know how to replace this tragetMutatedCopyObject in projObjs array of objects.
Many thanks in advance.
Updated Code :
projObjs.map(projObj => {
if (projObj.id === projectObjectId) {
const targetCopyObject = projObj.modifiedObject || projObj.originalObject;
const mutatedCopyObject = transformFormStateToMutationObject(targetCopyObject);
if (projObj.modifiedObject) {
return {
...projObj,
modifiedObject: mutatedCopyObject
};
}
if (projObj.originalObject) {
return {
...projObj,
originalObject: mutatedCopyObject
};
}
return projObj;
}
return projObj;
});
}`
Generally you would copy, or map, the old object to a new object reference. If the id matches the currently mapped element, return new object with the updated/modified properties, otherwise return the current element.
projObjs.map((projObj) => {
if (projObj.id === projectObjectId) {
const targetCopyObject = projObj.modifiedObject || projObj.originalObject;
const targetMutatedCopyObject = cloneDeep(targetCopyObject);
if (!targetMutatedCopyObject?.glazingOrGasMaterials.length) {
targetMutatedCopyObject.glazingOrGasMaterials = [
...targetMutatedCopyObject.glazingGasMaterials,
...targetMutatedCopyObject.glazingSimpleMaterials
];
}
targetMutatedCopyObject.opaqueConstructions.forEach(transformConstructions);
return {
...projObj,
modifiedObject: targetCopyObject
};
}
return projObj;
});
Edit to return updated objects back into their original object keys
Factor the object update logic into a utility function and apply some branching logic on the modifiedObject and originalObject values in order to return an updated object back to the appropriate key.
projObjs.map((projObj) => {
if (projObj.id === projectObjectId) {
const updateObject = (targetCopyObject) => {
const targetMutatedCopyObject = cloneDeep(targetCopyObject);
if (!targetMutatedCopyObject?.glazingOrGasMaterials.length) {
targetMutatedCopyObject.glazingOrGasMaterials = [
...targetMutatedCopyObject.glazingGasMaterials,
...targetMutatedCopyObject.glazingSimpleMaterials
];
}
targetMutatedCopyObject.opaqueConstructions.forEach(
transformConstructions
);
return targetMutatedCopyObject;
};
if (projObj.modifiedObject) {
return {
...projObj,
modifiedObject: updateObject(projObj.modifiedObject)
};
}
if (projObj.originalObject) {
return {
...projObj,
originalObject: updateObject(projObj.originalObject)
};
}
return projObj;
}
return projObj;
});
Note: Be sure to capture the returned result from projObjs.map to update any parent object. This will be the new updated array.

Javascript Loops: for-loop works but not map?

I'm working with mockData for a web app and I'm trying to loop over nested objects. My problem is that a for loop works but not array.map and don't know why.
Here is the for loop:
for (let i = 0; i < fakeChartData.length; i++) {
for (let j = 0; j < fakeChartData[i].poll.length; j++) {
if (fakeChartData[i].poll[j].id === id) {
return fakeChartData[i].poll[j]
}
}
}
And here is the map loop:
fakeChartData.map(data => {
data.poll.map(data => {
if (data.id === id) {
return data;
}
});
});
My Data structure:
fakeChartData = [
{
id: '232fsd23rw3sdf23r',
title: 'blabla',
poll: [{}, {}]
},
{
id: '23dgsdfg3433sdf23r',
title: 'againBla',
poll: [{}, {}]
}
];
I'm trying to load the specific object with the id passed to it on onClick method.
Here is the full function:
export const fetchPollOptById = (id) =>
delay(500).then(() => {
for (let i = 0; i < fakeChartData.length; i++) {
for (let j = 0; j < fakeChartData[i].poll.length; j++) {
if (fakeChartData[i].poll[j].id === id) {
return fakeChartData[i].poll[j]
}
}
}
});
A return statement inside a for loop causes your function to return. However, a return statement inside a .map() function's callback only returns the callback and this returned value is then placed in the new array. Please see the documentation.If you really want to be using .map(), you could do it like this:
export const fetchPollOptById = (id) => {
var result;
fakeChartData.map(data => {
data.poll.map(data => {
if (data.id === id) {
result = data;
return data;
}
});
});
return result;
}
note: I also assume that your poll objects have an id field like this:
fakeChartData = [
{
id: '232fsd23rw3sdf23r',
title: 'blabla',
poll: [
{id: 'pollId1', otherField: 'blah'},
{id: 'pollId2', otherField: 'blah'}
]
},
{
id: '23dgsdfg3433sdf23r',
title: 'againBla',
poll: [
{id: 'pollId3', otherField: 'blah'},
{id: 'pollId4', otherField: 'blah'}
]
}
];
You can then get the poll data like this:
fetchPollOptById("pollId3"); //returns {id: "pollId3", otherField: "blah"}
If I'm right about what you're trying to do, this should work:
return fakeChartData.reduce((acc, data) => acc.concat(data.poll), [])
.filter(pollObj => pollObj.id === id)[0]
First it makes an array containing all the poll objects from different data objects, then it filters them to find the one with the correct id and returns that object.
As to why your approach using map does not work: you are using it in the wrong way. What map does it to take a function and apply it to every member of an array.
Here's an array and function kind of like yours:
const arr = [1,2,3]
const getThingById(id) => {
var mappedArray = arr.map(x => {
if(x === id) return x
})
console.log(mappedArray) // [3]
}
getThingById(3) // undefined
This won't work. getThingById has no return statement. The return statement return x is returning something from the function that is passed into map. Basically, you shouldn't be using map to do what you're trying to do. map is for when you want to return an array.
Try this
fakeChartData.map(data => {
var result = data.poll.map(data => {
if (data.id === id) {
return data;
}
});
return result;
});
It should work. And yeah you should use find() instead of map() I think.
A bit long implementation:
let results = fakeChartData.map(data => {
let innerResult = data.poll.filter(data => {
if (data.id === id) {
return data;
}
return innerResult.length ? innerResult[0] : null;
});
})
.filter(x => (x !== null));
let whatYouwant = results.lenght ? results[0] : null;
If you can use find() it would look nicer, but that depends on what browsers you need to support

Categories