Returning String from Method Produces Undefined - javascript

I have an ES2015 module:
export default class ArrayCollection {
constructor(items = []) {
this.items = items;
}
search(query) {
this.items.forEach(function (item, index) {
if (query == item) {
return `Your query ${query} was found in the array on the ${index} index`;
};
});
return `Your array does not contain a ${query}`;
}
}
And in my main.js I have this:
import ArrayCollection from './ArrayCollection';
let Numbers = new ArrayCollection([2, 4, 6, 8, 10]);
let searchResult = Numbers.search(4);
console.log(searchResult);
Why does the console.log return undefined?
I am trying to search an array for an item and if it is there to return it. I know that there are special methods in ES6, but I just want to know what is wrong with the above code.
Thanks.
=== EDIT ===
If you run above ES6 code through webpack or rollup it produces the following vanilla code that runs through any browser:
var ArrayCollection = function ArrayCollection(items) {
if ( items === void 0 ) items = [];
this.items = items;
};
ArrayCollection.prototype.search = function search (query) {
this.items.forEach(function (item, index) {
if (query == item) {
return ("Your query " + query + " was found in the array on the " + index + " index");
}
});
// return `Your array does not contain a ${query}`;
};
var Numbers = new ArrayCollection([2, 4, 6, 8, 10]);
var searchResult = Numbers.search(4);
alert(searchResult);
Here is the JsFiddle that produces the same error. It would be nice if I could get a correction on the ES6 version instead of the compiled version.

There are a two issues with your code:
Returning inside the forEach callback will only exit the callback, not the search function. Meaning even if you return within the callback, the result will always be Your array does not contain a 4.
Be careful with the == operator. Unless you're very familiar with coercion, I would recommend use use the triple equal operator instead (===). See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators
You could write your code even simpler and avoid your issues using .indexOf:
export default class ArrayCollection {
constructor(items = []) {
this.items = items;
}
search(query) {
if (this.items.indexOf(query) > 0) {
return `Your query ${query} was found in the array on the ${index} index`;
};
return `Your array does not contain a ${query}`;
}
}

Related

Indexed getter in Javascript

I'm using straight Javascript (no JQuery or anything like that, please). I've implemented a class which wraps an array, thus:
class Ctrls
{
_items = new Array();
constructor()
{
this._items = new Array();
}
Add(oCtrl)
{
this._items.push( { key:oCtrl.Name, value:oCtrl } );
}
Clear()
{
this._items = new Array();
}
get Count()
{
return this._items.length;
}
get Item(index)
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
get Items()
{
return this._items; // in case we desperately need to
}
}
I get an error on page load, at get Item(index), which is Uncaught SyntaxError: Getter must not have any formal parameters. I come from C# world and am looking for an equivalent of:
public Ctrl Item(iIndex)
{
get
{
return _items[iIndex];
}
}
How do I index a getter in Javascript?
Edit(1): I've had suggestions to turn get Item into a function, but if I change the definition to this:
function GetItem(index) // returns Ctrl
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
I get this error on pageload: Uncaught SyntaxError: Unexpected identifier at the line function GetItem...
Edit(2): Modified the above to read:
GetItem(index) // returns Ctrl
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
as functions within classes do not use the function keyword, oddly. This now works. Thank all.
"you can't pass parameters to getters in JS". Theoretically: yes, you cannot do that. But practically: functions are first-class citizens in JS, so they can be arguments or return values of a function. You can do it like this:
class GetterWithParameter {
constructor() {
this.array = ["index 0", "index 1", "index 2"]
}
get itemAtIndex() {
return (idx) => this.array[idx]
}
}
const getterWithParameter = new GetterWithParameter()
const idx0 = getterWithParameter.itemAtIndex(0)
const idx1 = getterWithParameter.itemAtIndex(1)
const idx2 = getterWithParameter.itemAtIndex(2)
console.log("item at index 0:", idx0)
console.log("item at index 1:", idx1)
console.log("item at index 2:", idx2)
So, while the getter cannot have arguments, you can return a function that can receive an argument - and use that.
Of course, the usage seems identical to defining a function on the class that requires the same argument - but still, you are using a getter.

How to use array.prototype.reduce() with TypeScript while having a boolean accumulator as the initial value?

I was going about solving some algos on binarysearch.io and came across this problem:
I solved it using JS as follows:
class Solution {
solve(nums) {
const Hash = {};
const Cache = {};
for (let val of nums) {
if (!Hash[val]) {
Hash[val] = 0;
}
Hash[val]++; //implicit else
}
const result = Object.values(Hash).reduce((accum, curr) => {
if (accum && Cache[curr] !== 'unique') {
Cache[curr] = 'unique';
return true
}
return false;
}, true);
return result;
}
}
However, when I tried to solve it with TypeScript, which I'm fairly new to, and I had compilation errors likely caused by the .reduce() method:
class Solution {
solve(nums: Array<number>): boolean {
const Hash = {};
const Cache = {};
for (let val of nums) {
if (!Hash[val]) {
Hash[val] = 0;
}
Hash[val]++; //implicit else
}
const val_arr = Object.values(Hash);
return val_arr.reduce((accum, curr) => {
if (accum && Cache[curr] !== 'unique') {
Cache[curr] = 'unique';
return true;
}
return false;
}, true);
}
}
I'm not certain how to provide the .reduce() method a return type for the seeded initial value. I've tried changing the initial value to have a boolean type, but I still have a compilation error:
return val_arr.reduce((accum, curr) => {
if (accum && Cache[curr] !== 'unique') {
Cache[curr] = 'unique';
return true;
}
return false;
}, true as boolean);
I tried searching if anyone else has this problem, but most posts I've found have objects as the initial value, so I couldn't quite figure it out. It almost appears like the easiest solution is to solve this is to refactor to solve this without using .reduce(), but I simply want to know how to create a TS 'equivalent' to the aforementioned JS solution.
You need to
(1) Type the Hash object properly so that the type of its keys and values can be recognized by TS
(2) Type the Cache object properly so that the type of its keys and values can be recognized by TS
(3) Type the accumulator as a boolean, rather than true, so that the callback inside can return either true or false. (This can be done by passing <boolean> as the type argument to .reduce - using generics is a bit preferable to as)
class Solution {
solve(nums: Array<number>): boolean {
const Hash: {[key: number]: number} = {};
const Cache: {[key: number]: string} = {};
for (let val of nums) {
if (!Hash[val]) {
Hash[val] = 0;
}
Hash[val]++; //implicit else
}
const val_arr = Object.values(Hash);
return val_arr.reduce<boolean>((accum, curr) => {
if (accum && Cache[curr] !== 'unique') {
Cache[curr] = 'unique';
return true;
}
return false;
}, true);
}
}
Another thing to consider - JavaScript is not Java. If you have a class that just contains a single method, with no references to instance variables, it would make much more sense structurally to define a plain function instead, eg:
function solve(nums: Array<number>) {
// function implementation
}

How to deal with a `Variable 'xxx' is used before being assigned.`

I have a code block like below where I need to find something inside a loop, and also return a second variable. So I can't use a simple Array.find or Array.some good ole' for...of is my friend. map/filter don't allow a break and find can only return actual elements from the array, not a related calculation.
But the below within typescript is giving me an unavoidable error.
I'm wondering if either there's a more idiomatic way to do this, or a better structure / place to declare the variable?
Variable 'found' is used before being assigned.
let found: ParseResult
// breaks when first item found but we capture a different value
for (const rule of ParserRules) {
// const rex = new RegExp(route.match)
const parsed = rule.rex.exec(input)
if (parsed) {
found = { parsed, rule }
break
}
}
// #ts-ignore
return found // FIXME used before defined?
Here are the various JS iterator methods I tried...
const ar = [1, 2, 3, 4]
const log = console.log
const finder = (list) => {
console.log('map', list.map(it => it === 3))
console.log('find', list.find(it => it === 3))
console.log('some', list.some(it => it === 3))
console.log('filter', list.filter(it => it === 3))
console.log('find', list.find(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
console.log('filter', list.filter(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
const arr = list.forEach((k) => {
if (k === 3) return ('here')
})
log('arr', arr)
let found
for (const elem of list) {
log('elem of', elem)
if (elem === 2) {
found = elem
break
}
}
log('found', found)
}
finder(ar)
The summary of your problem is that a function returning a value in a variable when, at times, the logic doesn't get a chance to assign any value to it.
You can either initialize the variable with a default value or, at the point of returning, check if it really has a value.
let found: ParseResult= {}
OR
return found || false //Or an empty object etc
This can be done in many ways but what might suit your case would be
ar
.map((rule) => {
const parsed = rule.rex.exec(input)
if (parsed) {
return { parsed, rule }
}
})
.find((x) => !!x)
Yes you are looping it once more but this is more readable. Also it would not be that costly.
If your processing is heavy, you can try this approach as well but this will be a custom implementation and will not come out of the box:
function getParsedValue(ar, input) {
let parsed;
const rule = ar
.find((rule) => {
parsed = rule.rex.exec(input);
return !!parsed;
})
return !!rule ? { rule, parsed } : null
}

How to return a list of objects from a function inside a loop

I'm trying to loop over a list of views, and for each view retrieve a list of objects associated with that view, using a service call. Each view is being assigned the result of the last call to the function, instead of the result of the function call with its parameters.
Debugging output statements in the service layer method show that it is fetching the correct values. Adding a call to the method after the loop updates the views to use the results of that call.
I'm experienced programming in Angular2 and I've never come across this issue before, but I can't see what I'm doing differently. It seems the view property is being assigned the function rather than the function result.
Searching the issue suggests it's a closure issue, but I couldn't get any of the traditional solutions for this to work.
Here's what I have:
views.forEach((view: PeriodSummaryView) => {
view.CategorySummaries = API.getCategorySummariesByPeriod(view.Period, new Date()); // every view is given the result of the last evaluation of this function
view.TotalSpent = this.sumAmounts('Spent', view.CategorySummaries);
view.TotalBudgeted = this.sumAmounts('Budgeted', view.CategorySummaries);
});
and the API layer:
export default class API {
static getCategorySummariesByPeriod(filterPeriod: Period, filterDate: Date): CategorySummary[] {
var self = this;
let summaries: CategorySummary[] = Categories.slice();
summaries.forEach((category: CategorySummary) => {
category.Spent = Expenses.filter(function (e) {
return e.CategoryId == category.CategoryId
&& self.isDateInPeriod(filterPeriod, filterDate, e.Date)
}).reduce(function (acc, e) {
return acc + e.Cost;
}, 0);
});
return summaries;
}
}
Expected: Each view should have its own list of CategorySummaries, fetched from the API method using its parameters.
Actual: All views have the same list; the result from the last call to the API method.
Note: TotalSpent and TotalBudgeted are being calculated correctly.
The forEach function don't modify the original array. You must to create a new empty array and push each calculed item to the new array and return it.
export default class API {
static getCategorySummariesByPeriod(filterPeriod: Period, filterDate: Date): CategorySummary[] {
var self = this;
let summaries: CategorySummary[] = Categories.slice();
let new_arr = [];
summaries.forEach((category: CategorySummary) => {
category.Spent = Expenses.filter(function (e) {
return e.CategoryId == category.CategoryId
&& self.isDateInPeriod(filterPeriod, filterDate, e.Date)
}).reduce(function (acc, e) {
return acc + e.Cost;
}, 0);
new_arr.push(category);
});
return new_arr;
}
}
Looks like you may want to try using the .map method instead of .forEach. The return value of your provided function callback will be used to create the new array.
Example in TypeScript:
const oddNumbers = [1, 3, 5, 7, 9]
let evenNumbers = oddNumbers.map((oddNumber: number) => {
return oddNumber + 1;
});
// evenNumbers = [2, 4, 6, 8, 10]
// oddNumbers = [1, 3, 5, 7, 9 ]

React - Filtering returns wrong rows

It's driving me crazy. I've created a list with several entries. I added a filtering function, which seems to work fine. I've checked the number of results returned, but somehow it just showing the result number beginning at the first row.
For explanation:
Let's assume I search for "Zonen" and my filter function returns 4 rows with ID 23, 25, 59 and 60, the rows with ID's 1,2,3 and 4 are displayed. What I'm doing wrong!?
...
render() {
let filteredList = this.state.freights.filter((freight) => {
let search = this.state.search.toLowerCase();
var values = Object.keys(freight).map(function(itm) { return freight[itm]; });
var flag = false;
values.forEach((val) => {
if(val != undefined && typeof val === 'object') {
var objval = Object.keys(val).map(function(objitm) { return val[objitm]; });
objval.forEach((objvalue) => {
if(objvalue != undefined && objvalue.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
});
}
else {
if(val != undefined && val.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
}
});
if(flag)
return freight;
});
...
<tbody>
{
filteredList.map((freight)=> {
return (
<Freight freight={freight} onClick={this.handleFreightClick.bind(this)} key={freight.id} />
);
})
}
</tbody>
...
UPDATE
freights is loaded and filled via AJAX JSON result. One object of freights looks like this:
I have a textbox where a user can perform a search. This search should return all freight objects which properties contain the search string.
The filter is so complex, because I want to also to search in sub-objects of freight. Maybe there is a more simple way?
"Zones" was just an example for a search string the user can search for.
Now that your intentions are clearer, I suggest this much less complex solution.
First, you can write a recursive utility fn to get all values of all keys in an n-depth object. Like this, for example (I'm using lodash's utility fn isObject there):
const getAllValues = (obj) => {
return Object.keys(obj).reduce(function(a, b) {
const keyValue = obj[b];
if (_.isObject(keyValue)){
return a.concat(getAllValues(keyValue));
} else {
return a.concat(keyValue);
}
}, []);
}
Now that you have an array of all object's values, it makes your filter very simple:
let filteredList = this.state.freights.filter((freightItem) => {
const allItemValues = getAllValues(freightItem);
return allItemValues.includes(this.state.search);
});
That should be it. If something is not working, gimme a shout.
I have found the solution why the "wrong" freight entries are displayed.
I needed to add in freight component the componentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.freight) {
this.setState({
freight: nextProps.freight
});
}
}
Then everything worked fine.

Categories

Resources