Description
I've got a response coming back from a Service which I am calling on my NODE back-end.
I am then transforming my response in a more friendly format, like so;
export default (response) => {
const {
limit,
debt,
outstanding
} = response.object;
const transformedData = {
outstanding: retrieveAmounts(outstanding),
limit: retrieveAmounts(limit),
debt: retrieveAmounts(debt)
};
return _omitBy(transformedData, value => value === null);
};
As you can see I am running a function retrieveAmounts(value) on each item.
const retrieveAmounts = ({ amount, code }) => ({
amount: isStringDefined(amount) ? amount : null,
currencyCode: isStringDefined(code) ? code : null
});
Update
retrieveAmounts() in turn calls isDefinedString which checks the type and length provided, like so;
const isDefinedString = value => typeof value === 'string' && value.length > 0;
The problem is that while both; debt and limit are required and will always return - outstanding is not. If there is nothing to display the object key will simply not be there.
This is when the retrieveAmounts() throws an error because it cannot fin amount or code of undefined - since they don't exist because the key is not there.
How can I format the object key if it exists but return null if it does not? This way, retrieveAmounts() will not throw an error and I will simply provide limit:null to my front-end app.
'undefined' cannot be destructured. You need to check whether it's undefined before destructuring.
let limit = {'amount': '1000', 'code': '£'}, outstanding = undefined, debt = {'amount': '900', 'code': '£'};
const retrieveAmounts = amountStruct => {
if (typeof amountStruct === 'undefined')
return null;
let {amount, code} = amountStruct;
return {
amount: isStringDefined(amount) ? amount : null,
currencyCode: isStringDefined(code) ? code : null
};
};
Inline following #alex35's code.
const retrieveAmounts = (amountStruct) => ((typeof amountStruct === 'undefined') ? null : {
amount: isStringDefined(amountStruct.amount) ? amountStruct.amount : null,
currencyCode: isStringDefined(amountStruct.code) ? amountStruct.code : null
});
You could put a default parameter inside your retrieveAmounts function, so putting undefined inside your retrieveAmounts function doesn't produce an error.
const retrieveAmounts = (outstanding = { amount: null, code: null }) => ({ // << here
amount: isStringDefined(outstanding.amount) ? amount : null,
currencyCode: isStringDefined(outstanding.code) ? code : null
});
Related
So I have this function that in theory should filter an array of movies by a given genre, but i get this error:
TypeError: movie.genres.some is not a function.
(in 'movie.genres.some(function(item){return item.name === genre;})',
'movie.genres.some' is undefined) `
Movie class =>
title: string,
...,
genres: Genre[]
Genre class =>
id: number,
name: string
FilterMovies = (genre: string) => {
let fmovies: Movie[] = this.state.movies.filter((movie) => {
let data = movie.genres.some((item) => item.name === genre);
return data;
});
Am I doing this stuff right or did i mess up one of the functions? Any help would be very much appreciated!
edit: here's an example of a movie object
From the error message you're getting. The genres property of the Movie class is not really an array of Genre as all arrays have a some property (assuming you didn't remove it). Most likely genres property is just a single Genre object (a typo).
as some comments suggested, i was able to solve the problem with the following:
found out my object wasn't an array so i used Object.values to get the collections values
i made sure to check if every movie has a genre object that is not null nor undefined
although it probably still isn't perfect yet it works as of now so here it is:
FilterMovies = (genre: string) => {
let fmovies: Movie[] = this.state.movies.filter((movie) => {
let genreObj;
movie.genres != null || typeof movie.genres !== "undefined"
? (genreObj = Object.values(movie.genres))
: null;
let genreNames: string[] = [];
genreObj != null || typeof genreObj !== "undefined"
? genreObj?.forEach((genre) => {
genreNames.push(genre.name);
})
: null;
let data = genreNames.some((item) => item === genre);
return data;
});
I have a function ,that i'm using in multiple components . It collects data from components and stores it in db. My problem is that function has a lot of arguments and some of them might not be used in some components . Take a look :
export default async function addUserLogs(account, service, terminal,isSaved,isSentToGateway,amount ) {
const obj = {
serviceID: service,
terminalID: terminal,
amountName: account,
amount: amount,
isSaved: isSaved,
isSentToGateway: isSentToGateway,
};
const db = await InitDb();
const tx = db.transaction('userLogs', 'readwrite');
const store = tx.objectStore('userLogs');
const index = await store.put(obj);
let params = {};
if (window.localStorage.getItem('params') !== null) {
params = JSON.parse(window.localStorage.getItem('params'));
}
window.localStorage.setItem(
'params',
JSON.stringify({ ...params, userLogIndex: index })
);
}
For example account and service arguments i'm passing to function in on one component, others arguments is not required . In another component I only need to pass amount argument, but I need to specify previous arguments to do not overwrite other values. But there is an error "account, service, terminal,isSaved,isSentToGateway is not defined". Could you please suggest me how to fix that problem
addUserLogs can receive an object with optional keys and merge the last received object with the new one:
export default async function addUserLogs(newParams) {
const lastParams = <get the last params from storage>;
const obj = Object.assign(lastParams, newParams);
...
}
// usage examples
addUserLogs({amountName: 'x', serviceID: 1, terminalID: 2, isSaved: true ,isSentToGateway: false, amount: 10});
addUserLogs({isSentToGateway: false, amount: 10});
addUserLogs({amountName: 'x', serviceID: 1, terminalID: 2});
If you want to be more declarative about addUserLogs signature you can do something link:
export default async function addUserLogs({amountName, serviceID, terminalID, isSaved ,isSentToGateway, amount}) {
const lastParams = <get the last params from storage>;
const newParams = {
amountName: amountName || lastParams.amountName,
serviceID: serviceID || lastParams.serviceID,
terminalID: terminalID || lastParams.terminalID,
isSaved: isSaved === undefined ? lastParams.isSaved : isSaved,
isSentToGateway: isSentToGateway === undefined ? lastParams.isSentToGateway : isSentToGateway,
amount: amount === undefined ? lastParams.amount : amount
};
...
}
In order to get ride of this and make your function more generic, You should provide default arguments to the function so that when you don't pass any the oject creation here
serviceID: service,
terminalID: terminal,
amountName: account,
amount: amount,
isSaved: isSaved,
isSentToGateway: isSentToGateway,
};
will not break. To do that you use the below format
export default async function addUserLogs(account="default", service = "default", terminal = "default value",isSaved = "default value",isSentToGateway = "default value",amount = "default Value" )
provide default only for variables that are not going to be required in other places when called.
Note when you call this function and provide params they will replace the default but when you don't the function will use the default as such your function doesn't break with undefined variables
in that example you can set null for some arguments that is not required and you don't want to use them when callig your function.
you can use some filters for your arguments like so:
export default async function addUserLogs(account, service, terminal,isSaved,isSentToGateway,amount ) {
const obj = {};
if(account) obj.amountName = account;
if(service) obj.serviceID = service;
if(terminal) obj.terminalID = terminal;
if(isSaved !== null) obj.isSaved = isSaved;
if(isSentToGateway !== null) obj.isSentToGateway = isSentToGateway;
if(amount) obj.amount = amount;
const db = await InitDb();
const tx = db.transaction('userLogs', 'readwrite');
const store = tx.objectStore('userLogs');
const index = await store.put(obj);
let params = {};
if (window.localStorage.getItem('params') !== null) {
params = JSON.parse(window.localStorage.getItem('params'));
}
window.localStorage.setItem(
'params',
JSON.stringify({ ...params, userLogIndex: index })
);
} ```
Hi i have a react component expenses-total.js and a corresponding test case expenses-total.test.js as shown below.
expenses-total.js
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
};
expenses-total.test.js
import selectExpensesTotal from '../../selectors/expenses-total';
const expenses = [
{
id: "1",
description: "gum",
amount: 321,
createdAt: 1000,
note: ""
},
{
id: "2",
description: "rent",
amount: 3212,
createdAt: 4000,
note: ""
},
{
id: "3",
description: "Coffee",
amount: 3214,
createdAt: 5000,
note: ""
}
];
test('Should return 0 if no expenses', ()=>{
const res = selectExpensesTotal([]);
expect(res).toBe(0);
});
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal(expenses[0]);
expect(res).toBe(321);
});
test('Should correctly add up multiple expenses',()=>{
const res = selectExpensesTotal(expenses);
expect(res).toBe(6747);
});
when i run the test case, its getting failed by giving an error
TypeError: expenses.map is not a function
I know the test case is correct but dont know what is wrong with thecomponent.
Could anyone please help me in fixing this error?
The problem is with if (expenses.length === 0) and the test case that uses selectExpensesTotal(expenses[0]):
expenses[0] passes an object, which has no length property, so in the function being tested, expenses.length returns undefined. However, undefined === 0 evaluates to false so your code goes into the else block tries to use .map on the object, which doesn't have that function, thus it throws an error.
In a brief: you can't map over an object.
expenses is an array of objects, so expenses[0] is an object.
Condition expenses.length === 0 evaluates to false, since obviously .length property does not exist on Object.prototype, so the else condition takes place - your function tries to map over an object.
The problem is that expenses[0] is an object (you probably expected it to be an array) and an object does not have a map function. A quick hack would be to add another ifs into the loop to check if expenses is actually an object. So that:
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
if (typeof expenses === 'object') {
return expenses.amount
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
}
};
I hope this help.
To fix this error, you can pass in an array of object into
selectExpensesTotal([expenses[0]])
rather than just an object
selectExpensesTotal(expenses[0])
So your code show look like this:
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal([expenses[0]]);
expect(res).toBe(321);
});
.map function will now work on expenses. Because, this is now an array of object ( works with map function ) and not an object(This does not work with map function)
I have a method wrapped in an observable in one of the components in my Angular 2 app that is designed to filter an array of results depending on the boolean value of a particular property. My method looks like this:
this.clientService.getAllClients()
.subscribe(resRecordsData => {
this.records = resRecordsData;
this.inactiveRecords = this.records.filter(record => record.registration.active === false);
this.records = this.inactiveRecords;
},
responseRecordsError => this.errorMsg = responseRecordsError);
When I run this I get an "undefined" error:
EXCEPTION: Cannot read property 'active' of undefined
I'm assuming this is arising because not all of the entries in the collection contain this property. So my question is, how can I add conditional logic to handle the lack of presence of this property I'm checking against in the array?
check whether the object conains the property first by:
record => record.registration && record.registration.active === false;
var testItems = [{
id: 1,
detail: {
name: 'test name1'
}
},{
id: 2,
detail: {
name: 'xxxx'
}
}, {
id: 3,
}];
console.log(testItems.filter(function(item) {
return item.detail && item.detail.name.indexOf('test') > -1;
}))
You can check if properties are defined on objects by using
obj.hasOwnProperty('foo')
So in your case you could do something like
this.inactiveRecords = this.records.filter(
record => {
let registration = record.hasOwnProperty('registration') ? record.registration : false;
if (registration && registration.hasOwnProperty('active')) {
return registration.active === false;
}
return false; // Default return for when property is not defined.
}
);
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
I have an Angular 2 typescript application that is using lodash for various things.
I have an array of objects that I am ordering using a property in the object...
_.orderBy(this.myArray, ['propertyName'], ['desc']);
This works well however my problem is that sometimes 'propertyName' can have a null value.
These are ordered as the first item in a descending list, the highest real values then follow.
I want to make these null values appear last in the descending ordering.
I understand why the nulls come first.
Does anyone know how to approach this?
The _.orderBy() function's iteratees can use a method instead of a string. Check the value, and if it's null return an empty string.
const myArray = [{ propertyName: 'cats' }, { propertyName: null }, { propertyName: 'dogs' }, { propertyName: 'rats' }, { propertyName: null }];
const result = _.orderBy(myArray, ({ propertyName }) => propertyName || '', ['desc']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The check can be simple (like the one I've used), which converts all falsy values to an empty string:
propertyName || ''
If you need a stricter check, you can use the ternary operator, and handle just null values:
propertyName === null ? '' : propertyName
Edit: Example with multiple ordering:
const result = _.orderBy(myArray, (item) => [get(item, 'propertyName', 0), get(item, 'propertyName2')], ['desc', 'asc']);
This will order by propertyName then propertyName2.
If propertyName is undefined/null then its default order will be set to 0. (and therefore will be displayed at last because of desc ordering on the propertyName field). In such case, propertyName2 will therefore determine the ordering.
The code I needed looks like this...
_.orderBy(this.myArray, [( o ) => { return o.myProperty || ''}], ['desc']);
Just for future reference to others you can do this to sort ascending with falsey values at the end.
items =>
orderBy(
items,
[
i => !!i.attributeToCheck,
i => {
return i.attributeToCheck ? i.attributeToCheck.toLowerCase() : ''
}
],
['desc', 'asc']
)
mine looks like this. PropName and sort are both variables in my solution
return _.orderBy( myarray, [
( data ) => {
if ( data[propName] === null ) {
data[propName] = "";
}
return data[propName].toLowerCase();
}
], [sort] );
I wanted tolowercase because otherwise the sorting is not correct if different casings
This will put bad values at the bottom, and it differentiates between numbers and strings.
const items = [] // some list
const goodValues = isAscending => ({ value }) => {
if (typeof value !== 'string' && isNaN(value)) {
return isAscending ? Infinity : -Infinity
}
return value || ''
}
const sortedItems = orderBy(
items,
[goodValues(isAscending), 'value'],
[isAscending ? 'asc' : 'desc']
)
This worked for me
orders = [{id : "1", name : "test"}, {id : "1"}];
sortBy = ["id", "name"];
orderby(
orders,
sortBy.map(s => {
return (r: any) => {
return r[s] ? r[s] : "";
};
})),
);
I created a function for this (ts code):
const orderByFix = (array: any[], orderKeys: string[], orderDirs: ('asc' | 'desc')[]) => {
const ordered = orderBy(array, orderKeys, orderDirs);
const withProp = ordered.filter((o) => orderKeys.every(k => o[k]));
const withoutProp = ordered.filter((o) => !orderKeys.every(k => o[k]));
return [...withProp, ...withoutProp];
};
I've extended gwendall's answer to also handle case when "order keys" are functions (_.orderBy allows that)
const orderByFix = (
array: any[],
orderKeys: (string | ((o: any) => any))[],
orderDirs: ('asc' | 'desc')[]
) => {
const ordered = orderBy(array, orderKeys, orderDirs)
const withProp = ordered.filter((o) =>
orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
const withoutProp = ordered.filter(
(o) =>
!orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
return [...withProp, ...withoutProp]
}