Using filter instead of foreach - javascript

I am currently trying to execute some operations after retrieving some data, the format I am trying to achieve is an array of strings:
let cleanedData = ['foo', 'bar']
The data I receive could either be an array of objects or an array of arrays that could have empty arrays that needs to be filtered out. So for example I can receive either: let notCleanedData = [['foo'],[],[],['bar']] or this let notCleanedData = [{var: 'foo'}, {var: 'bar'}]
This is my code, it is working but I would like to improve it in a cleaner way or with ES6+ methods I have tried to use a filter function without success, any advice?
function filterInputData(notCleanedData) {
let cleanedInputData = [];
notCleanedData.forEach(input => {
if (input.length > 0) {
cleanedInputData.push(input)
}
if (input.var) {
cleanedInputData.push(input.var)
}
});
return cleanedInputData;
}
console.log(
filterInputData([['foo'],[],[],['bar']])
)
console.log(
filterInputData([{var: 'foo'}, {var: 'bar'}])
)

This is a simple one-line solution that comes to my mind without using any libraries:
const clean = (data) => data.map(item => item.var || item[0]).filter(item => item)
I tried it on the test inputs you provided:
const clean = (data) => data.map(item => item.var || item[0]).filter(item => item)
console.log(
clean([['foo'],[],[],['bar']])
)
console.log(
clean([{var: 'foo'}, {var: 'bar'}])
)

const input = [['foo'],[],[],['bar']]
const input2 = [{var: 'foo'}, {var: 'bar'}]
const clean = (data) => data && data[0].length // check type
? data.flat(1) // use new flat method if array
: (data || []).map((e) => e.var) // map objects otherwise

Here is a much cleaner approach using reduce:-
// This function assumes that the array inside the array has single value and the object inside array has only one key called var
function filterInputData(notCleanedData) {
return notCleanedData.reduce((acc, item) => {
if (item.length) acc.push(item[0]);
if (item.var) acc.push(item.var);
return acc;
}, [])
}
Hope you find this useful.

I'd bring in lodash to help here.
You can use flattenDeep to clean and flatten and map to test if the data includes objects.
Example:
function clean(data) {
return
_.chain(data)
.flattenDeep()
.map(x => {
if (x.var) {
return x.var;
}
return x;
})
.value();
}
console.log(
clean([['foo'],[],[],['bar']])
)
console.log(
clean([{var: 'foo'}, {var: 'bar'}])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

In this situation you can use map to achieve what you want without foreach. Filter wont work since it can only remove elements from the list and not actually make any modifications to them.
function filterInputData(notCleanedData) {
let cleanedInputData = notCleanedData.map((input) => {
if (input.length > 0) {
return input;
}
if (input.var) {
return input.var;
}
}).filter(item => item !== undefined);
return cleanedInputData;
}

Related

How to filter an array by two indvidual strings from an object?

I'm working on a project where I need to filter 13 items by two different select box values, and I'm getting stuck on persisting the filter.
I have two select boxes that I've selected like so:
let pickupLocation = document.querySelector("#pa_location"); //values are 'complete-set', 'neck', 'bridge'.
let pickupType = document.querySelector("#pa_type1"); // Values are 'soapbar', 'dogear', 'short'.
What's Working:
I'm initializing an object like so:
const activeFilters = {};
To populate the values like so:
//Persist the Complete Set / Single
pickupLocation.addEventListener("change", function () {
if (pickupLocation.value === "complete-set") {
activeFilters.location = "set";
} else {
activeFilters.location = "single";
}
});
pickupType.addEventListener("change", function () {
if (pickupType.value === "soapbar") {
activeFilters.type = "soapbar";
} else if (pickupType.value === "dogear") {
activeFilters.type = "dogear";
} else {
activeFilters.type = "short";
}
});
// Returns something like
// {location: single, type: dogear}
I'm trying to filter an array of input elements by their value. I have 13 inputs each with a value containing words like set, single, dogear, soapbar etc.
Where I'm stuck:
I have a filter function that I'm trying to filter the values of these inputs by two values of the activeFilters object:
const performFilter = (covers) => {
let results;
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
results = covers.filter((cover) => cover.value.indexOf(value) !== -1);
});
return results;
};
The problem is my function is returning only one of the two words. For instance, if the my activeFilters object is {location: set, type: dogear} the filtered results array contains only one of them. Where am I going wrong?
Edit:
This function returns all inputs that match one of the activeFilters, and I apologize if I wasn't clear above, but I'd like it to match ALL of the Active Filters. Is this possible with the function below?
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
let res = covers.filter((cover) => cover.value.indexOf(value) !== -1);
results.push(...res);
});
console.log(results);
};
CODEPEN:
Codepen!
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
let res = covers.filter((cover) => cover.value.indexOf(value) !== -1);
// push the value it find individually
// you were overriding the previous value with result = filter()
results.push(...res);
});
return results;
};
// according to Edited question
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
return covers.filter((cover) => filteredValues.every(value => cover.value.indexOf(value) !== -1));
};
I'm not sure if I understood clearly your question, so feel free to comment it.
First, I suggest you to filter your covers array and inside the filtering function iterate through your selected filters. This is because the filter function returns the array already filtered and so you don't need to assign it to a result variable or things like that. So based on that, try this:
const performFilter = (covers) => {
let results;
let filteredValues = Object.values(activeFilters);
const filteredCovers = covers.filter((cover) => {
return cover.value.split("-").some((tag) => filteredValues.includes(tag))
});
console.log(filteredCovers)
};

Updating data in an Object using Firebase arrayUnion

I am looking to update data in an object without changing the index of the object within the array it is contained. As it currently stands, the code removes the current object from the array and then applies array Union to update the array but pushes the component to the end of the array but I am looking to just update the data without the component losing its index. This is the code I am currently working with, I looked through the Firebase docs to see if there was a way to just update the component but couldn't find anything if anyone could point me in the right direction, please.
await firestore.update(project, {
pages: firestore.FieldValue.arrayRemove(page),
});
await firestore.update(project, {
pages: firestore.FieldValue.arrayUnion(newPage),
});
Unfortunately there is no field transform to replace a value like this:
firestore.FieldValue.arrayReplace(page, newPage);
Storing arrays and making changes by index in remote databases is generally discouraged. This older Firebase blog post covers some of the reasons why even though it was written with the Firebase Realtime Database in mind.
If the order of that array is important, you have two options:
fetch the array, mutate it, and then write it back. (simple)
fetch the array, find the relevant index, update that index only. (difficult)
To achieve the first result, you would make use of a transaction to find the previous value and replace it:
const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId");
function replacePage(oldPage, newPage) {
return db.runTransaction(async (t) => {
const snapshot = await t.get(projectDocRef);
if (!snapshot.exists) {
// no previous data, abort.
return "aborted";
}
const pagesArray = snapshot.get("pages");
const index = pagesArray.findIndex((page) => page === oldPage);
if (index === -1)
return "not-found";
pagesArray[index] = newPage;
await t.set(projectDocRef, { pages: pagesArray }, { merge: true });
return "replaced";
});
}
replacePage("index", "shop")
.then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
.catch((err) => console.error('failed: ', err));
Note: Anything beyond this point is educational. There are many issues with this approach at scale.
Because Firestore doesn't support array entry replacement by index, you'll need to implement a way to update an index using something Firestore understands - maps. Using some FirestoreDataConverter trickery, you can use the converter to serialize your array as a map when you write it to Cloud Firestore and deserialize it back to an array when you read it. The major trade-off here is in how you will be able to query your data. You will be able to perform queries by index (such as where('pages.0', '==', 'shop')) but you'll lose the ability to use array-contains queries (such as where('pages', 'array-contains', 'shop')).
First, you need to define the converter:
// const obj = {};
// setNestedProperty(obj, ["a", "b", "c"], true)
// obj is now { "a": { "b": { "c": true } } }
function setNestedProperty(originalObj, pathPropsArray, val) {
const props = pathPropsArray.slice(0, -1);
const lastProp = pathPropsArray[pathPropsArray.length-1];
const parent = props.reduce((obj, p) => obj[p] ? obj[p] : (obj[p] = {}), originalObj);
parent[lastProp] = val;
}
const pagesArrayConverter = {
toFirestore(data) {
if (data.pages !== undefined) {
// step 1) convert array to map
const pagesAsMap = {};
data.pages.forEach((page, index) => {
if (page !== undefined) {
pagesAsMap[index] = page;
}
});
data.pages = pagesAsMap;
// step 2) if there are any mutations to "pages"
// while you are changing it, make the
// changes now before uploading to Firestore
Object.keys(data)
.filter(k => k.startsWith("pages."))
.forEach(k => {
const nestedValue = data[k];
data[k] = undefined;
delete data[k];
setNestedProperty(pagesAsMap, k.slice(6).split("."), nestedValue);
});
}
return data;
},
fromFirestore(snapshot, options) {
const data = snapshot.data(options);
if (data.pages !== undefined) {
const pagesAsArray = [];
Object.entries(data.pages)
.map(([index, page]) => pagesAsArray[index] = page);
// `pagesAsArray` may have empty elements, so we need
// to fill in the gaps with `undefined`:
data.pages = Array.from(pagesAsArray);
}
return data;
}
};
Which you would then attach to a query/reference like this:
const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId")
.withConverter(pagesArrayConverter)
If you already know that the previous value has an index of 2, you can just use:
await projectDocRef.set({ "pages.2": newPage }, { merge: true });
If you need to find it like before, you can use a transaction:
function replacePage(oldPage, newPage) {
return db.runTransaction(aysnc (t) => {
const snapshot = await t.get(projectDocRef);
if (!snapshot.exists) {
// no previous data, abort.
return "missing";
}
const data = snapshot.data();
// data is a { pages: Page[] }
const index = data.pages.findIndex((page) => page === oldPage);
if (index === -1)
return "not-found";
await t.set(projectDocRef, { ["pages." + oldIndex]: newPage }, { merge: true });
return "replaced";
});
}
replacePage("index", "shop")
.then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
.catch((err) => console.error('failed: ', err));
arrayUnion adds new items to the array and arrayRemove removes items from an array. There isn't any way to update an existing item in array directly.
You would have to fetch the document, manually add/update the item at relevant index and then update the whole array back to the document.

Checking existing array have given value or not

In following code i have define empty array houseTotal, now I would like to push value inside array which is unique and not exist previously. I have use some, unique, sort filter but its pushing all the value it gets. Here is my code:
let houseTotal = [];
await rel.map((each, index) => {
if (
!isEmpty(each.house_detail) ||
!houseTotal.some(el => el === each.house_detail._id)
) {
houseTotal.push(each.house_detail._id);
}
});
return houseTotal.length;
What I have done mistake here ? Thank you.
If houseTotal is to have UNIQUE values and no duplicates.. I'm going to assume "duplicates" can be == each other and I'll use the Array.includes function
let houseTotal = [];
await rel.map((each, index) => {
if (
!isEmpty(each.house_detail) ||
!houseTotal.some(el => el === each.house_detail._id)
) {
let detail=each.house_detail._id
if(!houseTotal.includes(detail)){houseTotal.push(detail);}
}
});
return houseTotal.length;
And I got the solution, it was small mistake I have made on above code, I forgot to change object id returning from mongo to string so just added toString().
let houseTotal = [];
await rel.map((each, index) => {
if (
!isEmpty(each.house_detail) ||
!houseTotal.some(el => el === each.house_detail._id.toString())
) {
houseTotal.push(each.house_detail._id.toString());
}
});
return houseTotal.length;

Return array value from produce function | immer.js

I am using immer.js to perform operations on arrays in the state.
Arrays: basicRecipe and recipeBasicRecipe.
I am modifying the draft.basicRecipe in the produce function. My objective is to return the updated "draft.basicRecipe" value and store the same in temparray1.
let temparray1 = produce(state, draft => {
draft.basicRecipe = draft.basicRecipe.map(item => {
let element = draft.recipeBasicRecipes.find(e => e._id === item._id);
console.log(element);
if (element) {
item.details = item.details.map(e => {
let detail = element.details.find(d => d._id === e._id);
if (detail) {
e.rate = detail.rate;
}
return e;
});
}
return item;
});
return draft.basicRecipe;
});
console.log(temparray1);
When I return the draft I am able to see updated basicRecipe nested in output.
I am getting the below error when I try to return the array i.e draft.basicRecipe
[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft
This code is a mess. You are using map which returns a new array but you're also trying to mutate the original draft object.
This is still unreadable and confusing, but at least by using forEach instead of map we are just mutating and not trying to do two things at once.
let temparray1 = produce(state, (draft) => {
draft.basicRecipe.forEach((item) => {
let element = draft.recipeBasicRecipes.find((e) => e._id === item._id);
if (element) {
item.details.forEach((e) => {
let detail = element.details.find((d) => d._id === e._id);
if (detail) {
e.rate = detail.rate;
}
});
}
});
});

How to get all properties values of a Javascript Nested Objects (without knowing the keys)?

In referencing how to do this i took a look at how-to-get-all-properties-values-of-a-javascript-object-without-knowing-the-key Using ES6
How would you do this if the object you are scanning into has child object where you need only the Values for those too, into an array.
example :
var errorData = {"Message":"The request is invalid.","ModelState":{"account.Contact.PrimaryPhone":["Primary Phone is required.","'Primary Phone' should not be empty."]}}
var errors = Object.keys(errorData).map(function (key) {
return errorData[key];
});
doesn't work.
i need an array that lists like this:
The request is invalid.
Primary Phone is required.
'Primary Phone' should not be empty.
The easiest way is probably with a recursive function. You can do that like this in a modern engine:
const errorData = {"Message":"The request is invalid.","ModelState":{"account.Contact.PrimaryPhone":["Primary Phone is required.","'Primary Phone' should not be empty."]}}
const errors = (function flattenValues( obj ) {
return Object.values( obj ).reduce(
( values, value ) => values.concat( typeof value === "object" ? flattenValues( value ) : value )
, [ ] );
} )( errorData );
console.log( errors );
although Object.values only has fairly recent browser support, so you may want to use something that has more compatibility instead:
var errorData = {"Message":"The request is invalid.","ModelState":{"account.Contact.PrimaryPhone":["Primary Phone is required.","'Primary Phone' should not be empty."]}}
var errors = (function flattenValues( obj ) {
return Object.keys( obj ).reduce( function ( keys, key ) {
var value = obj[key];
return values.concat( typeof value === "object" ? flattenValues( value ) : value );
}, [ ] );
} )( errorData );
console.log( errors );
There usually is a fixed structure to an API response, or at least a certain guideline. You could test for the type of the value, and see if it is an array or object. You could also anticipate a particular key in the response, test for its existence, and do work accordingly.
let errorData = {"Message":"The request is invalid.","ModelState":{"account.Contact.PrimaryPhone":["Primary Phone is required.","'Primary Phone' should not be empty."]}}
const flatten = (obj) => obj.reduce((acc, val) => acc.concat(val), []);
const unwrap = (obj) =>
Object.keys(obj).map((key) => {
if (typeof(obj[key]) == 'object')
return flatten(unwrap(obj[key]));
else
return obj[key];
});
let errors = flatten(unwrap(errorData));
This should work. Just through it together, haven't fully tested it.
Object.values(a)
.map(v => v instanceof Object ? Object.values(v) : [v])
.reduce((acc, next) => acc.concat(...next), [])

Categories

Resources