Match multiple object fields with an input value in JS - javascript

I am working on a large product datasets and I need to filter through the products list based on the user's input value. My product dataset looks something like this:
const products = [
{id: 0, name: "Product1", brand: "Theraflu", itemCode: "THE110", price: 5.45},
{id: 1, name: "Product2", brand: "Benadryl", itemCode: "BEN121", price: 7.05},
{id: 2, name: "Product3", brand: "Listerine", itemCode: "LIS204", price: 4.55},
{id: 3, name: "Product4", brand: "Tylenol", itemCode: "TYL116", price: 6.10},
];
I was able to filter the product list based on the different fields available in each individual product object like this:
const keys = ["name", "brand", "itemCode"];
const getFilteredProducts = (filterText) => {
const newProducts = products.filter(product => keys.some(key => product[key].toLowerCase().includes(filterText.toLowerCase())));
return newProducts;
}
console.log(getFilteredProducts("Tylenol"));
This code actually works when I filter the product based on individual field. However, when I try to combine different fields like:
console.log(getFilteredProducts("product4 Tylenol"));
The returned value of this is an empty array. Is there a way to achieve this without altering the existing filtering functionality?

Seems like you'll need to search each word of the filterText on its own. Maybe something like this:
const keys = ["name", "brand", "itemCode"];
const getFilteredProducts = (filterText) => {
const filterWords = filterText.split(" ");
const newProducts = [];
for (const word of filterWords) {
newProducts.push(
products.filter(product => keys.some(key =>
product[key].toLowerCase().includes(word.toLowerCase())
))
);
}
return [].concat(...newProducts).filter((value, index, self) =>
self.findIndex((m) => m.id === value.id) === index
);
}
console.log(getFilteredProducts("Tylenol Product3"));

Related

Add product with array of options to cart

I have a product. I add it to my cart. If such an item exists in the shopping cart, I will increase its counter. It's simple, checking by id.
I have a product, it has an array of options. (milk, cream, sugar, etc.) How to check the cart, and find an existing copy of the product with such options?
function addCoffeeToCart(coffee: CartItem) {
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) => cartItem.id === coffee.id
);
const newCart = produce(cartItems, (draft) => {
if (coffeeAlreadyExistsInCart < 0) {
draft.push(coffee);
} else {
draft[coffeeAlreadyExistsInCart].quantity += coffee.quantity;
}
});
setCartItems(newCart);
}
Product:
export interface Coffee {
id: number;
tags: string[];
name: string;
description: string;
photo: string;
price: number;
options: Options[];
}
Options:
export interface Options{
id: number;
name: string;
price: number;
}
Change coffeeAlreadyExistsInCart to this and try again:
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) =>
cartItem.id === coffee.id &&
JSON.stringify(cartItem.options) === JSON.stringify(coffee.options)
);
the simplest way of dealing with the count / if exist - is to use an object literal as a map of the existing items in the cart. Then you can have an array of the items with their salient details, and use the object literal to keep track of the count (which automatically deals with the if-exists issue) and then create a cart and a running total of the toal price etc.
The advantage of this approach is that it separates the count logic from the item details in your store and allows for different id and display... eg you can have an id of "c1234-1" and a displayText of "Coffee" and this will still allow you to have your calculartions. Its also good to separate your items details from the cart / store - so that you can easily update prices or availability etc.
Deleting items from the cart is simple - just decrement the count of the item down to 0 and this will adjust the cart total as you do so.
const items = [
{id: 'coffee', name: 'Coffee', price: 5.5},
{id: 'tea', name: 'Tea', price: 3.5},
{id: 'cocoa', name: 'Cocoa', price: 2.5},
{id: 'milk', name: 'Milk', price: .5},
{id: 'sugar', name: 'Sugar', price: 0},
]
const cartItems = {};
function addItemToCart(id) {
cartItems[id]
? cartItems[id] ++
: cartItems[id] = 1;
setCart()
}
function setCart() {
const cart = {total: 0, items:[]};
Object.keys(cartItems).forEach(id => {
const item = items.find(x => x.id === id);
const itemQty = cartItems[id];
cart.items.push({name: item.name, quantity: itemQty, subTotal: item.price * itemQty });
cart.total+= (item.price * itemQty);
})
console.log(cart)
}
addItemToCart('coffee');
// gives {"total": 5.5, "items": [{"name": "Coffee","quantity": 1, "subTotal": 5.5}]}
addItemToCart('coffee');
// gives { "total": 11, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11}]}
addItemToCart('tea');
// gives { "total": 14.5, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": Tea","quantity": 1,"subTotal": 3.5} ] }
addItemToCart('milk');
// gives { "total": 15, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": "Tea","quantity": 1,"subTotal": 3.5},{"name": "Milk","quantity": 1,"subTotal": 0.5} ]}}
addItemToCart('sugar');
// gives { "total": 15, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": "Tea","quantity": 1,"subTotal": 3.5},{"name": "Milk","quantity": 1,"subTotal": 0.5},{"name": "Sugar","quantity": 1,"subTotal": 0} ] }
On the condition of the findIndex you have to add that verification. As answered you can do it with Json.stringify of both arrays but that assumes they are on the same order and complety equal. I prefer to use Array.every and Array.some to ignore those constraints at cost of a little of performance.
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) =>
cartItem.id === coffee.id &&
cartItem.options.length === coffee.options.length &&
coffee.options.every(item => cartItem.options.some(elem => elem.id === item.id))
);
Basically with this we are "asking" to each CartItem if they have the same number of options as coffee ( dont know if as to be like that, if you want to just check if have the all the options but not if they have the same number of options you can ignore that condition ) and if every option on the cartItem exists on teh coffee options

Given two array create another one with only different element

I have two array:
for example:
arraySelectedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
now I need to check if there is some item in arraySavedItems that is not present in arraySelectedItems, and in this case I'll go to populate another array called arrayDeletedItems.
If the two arrays have the same items I don't need to populate the arrayDeletedItems.
So I have tried with this code:
arraySavedItems.filter((itemSaved) => !arraySelectedItems.find((itemSel) => {
if (itemSaved.id !== itemSel.id) {
arrayDeletedItems.push(itemSaved)
}
}
))
So with this data:
arraySelectedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
I'll expect that arrayDeletedItems will be:
arrayDeletedItems = []
Instead whit this data for example:
arraySelectedItems = [{id: 1, name: "item1"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
I'll expect that arrayDeletedItems will be:
arrayDeletedItems = [{id: 2, name: "item2"}]
With my code I receive and arrayDeletedItems that has the all values:
arrayDeletedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
Consider this generic function:
function difference(a, b, keyFn) {
let keys = new Set(a.map(keyFn))
return b.filter(obj => !keys.has(keyFn(obj)))
}
//
selectedItems = [{id: 1, name: "item1"}, {id:4}]
savedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}, {id:3}, {id:4}]
result = difference(selectedItems, savedItems, obj => obj.id)
console.log(result)
You can use the .includes() method on an array to check whether a value is contained in it (see the documentation for more information).
Now we can just filter the array of saved items to find only ones that aren't contained by the selected items array.
arrayDeletedItems = arraySavedItems.filter((itemSaved) =>
!arraySelectedItems.includes(itemSaved)
)
As #owenizedd points out in the comments, this only works for primitive data types where a shallow equality check is sufficient. A more robust approach can be used with the .reduce() method and a custom equality check. For example, lodash's isEqual() does a deep comparison for equality. You would have to import the module for this. Unfortunately there is no native deep equality check in JavaScript currently (workarounds like JSON.stringify() to then compare the string representations have various downsides).
arrayDeletedItems = arraySavedItems.filter((itemSaved) =>
!arraySelectedItems.reduce((previous, current) =>
previous || _.isEqual(current, itemSaved)
)
)
Note that passing previous as the first argument to the 'or' operator (||) means we can benefit from lazy evaluation - once a hit has been found, the second half of the statement does not need to be evaluated any more.
To solve this problem, since we have id we can utilize it.
You need a key that is unique. so id commonly known will have unique value.
So my approach, find items that is not exist in B array but in A array, and find items that exist in B but not in A array.
This approach not be the fastest, but the findDiff is reusable.
const a = [....];
const b = [....];
const findDiff = (source, target) => {
return source.filter((sourceItem, index) => {
const isInTarget = target.findIndex(targetItem => targetItem.id === sourceItem.id)
return isInTarget === -1
})
}
const difference = findDiff(a,b).concat(findDiff(b,a)); //result

Check arrays to find equal id

I have a list of products like:
listOfProducts = [{name: "prod1", productId: 1}, {name:"prod2", productId: 2},.....]
If I choose a product i'll populate the array
listSelectedProducts
it will contains the choosen products.
And I make a post to save my choise.
Now If I go back in the products page, i'll have an array:
oldProductsArray
which contains the products that I have saved in db.
listSelectedProducts
with my selected products.
Now If I remove the selected product to choose another one, my oldProductsArray will have the first product, but listSelectedProducts will have the new product choose.
So now I should remove from my db the product that I don't want anymore (an it is in oldProductsArray). So I thought to compare (and there I have my problem) the two arrays and if elements in oldProductsArray are not in listSelectedProducts, i'll create a new array with the products not selected to delete them.
So let's do an example:
I'm in my products page and choose a product.
So listSelectedProducts = [{name: "prod1", productId: 1}]
and I will post in db.
I return in products page and this time I have:
listSelectedProducts = [{name: "prod1", productId: 1}]
oldProductsArray = [{name: "prod1", productId: 1}]
I deselect the product: prod1 and choose the product: prod2
So I have:
listSelectedProducts = [{name: "prod2", productId: 2}]
oldProductsArray = [{name: "prod1", productId: 1}]
and now I should check if products in oldProductsArray are also in listSelectedProducts, if they are I can do the post, if they are not (like in this case) I should remove from db the product.
So I have a function:
this.checkOldProduct(oldProductsArray, listSelectedProducts)
in this function:
checkOldProduct(oldProductsArray, listSelectedProducts){
let arrayProductToDelete = []
// i have tried like this, but it doesn't work properly.
listSelectedProducts.filter(p1 => !oldProducts.some(p2 => { p1.productId === p2.productId, arrayProductToDelete.push(p2) }));
}
I hope I have expressed myself well, in any case I am ready to add clarifications. thank you
some quick consideration first:
If that's really all the data you have for the product list, you should probably just use a PUT method on your API and simply replace the whole list without having to compute the difference
If you still somehow need to make separate operations to update products list, I guess you should make POST requests to add new items, DELETE requests to delete items and PATCH requests to update single items (like same id but different quantities?)
From the point above: do you also have quantities for items?
Question Specific Answer
So based solely on your question I think easiest way is to find the list of items to delete something like this:
const removedItems = oldArray.filter((old) => !newArray.find((_new) => old.id === _new.id));
And request their deletion.
Full Diff Answer
If you want to compute a full diff of your chart items so you can make multiple update requests, you could do something like this:
function arrayDiff(oldArray, newArray) {
const addedAndUpdatedItems = newArray.reduce((diff, item, index, array) => {
const oldItem = oldArray.find((old) => old.id === item.id);
if(!oldItem) {
diff.added.push(item);
} else if(oldItem.quantity !== item.quantity) {
diff.updated.push(item);
}
return diff;
}, {
added: [],
updated: []
});
const removedItems = oldArray.filter((old) => !newArray.find((_new) => old.id === _new.id));
return {
...addedAndUpdatedItems,
removed: removedItems
}
}
const oldArray = [{ name: "prod1", id: 1, quantity: 1 }, { name: "prod3", id: 3, quantity: 4 }];
const newArray = [{ name: "prod1", id: 1, quantity: 3 }, { name: "prod2", id: 2, quantity: 3 }];
const diff = arrayDiff(oldArray, newArray);
console.log({ diff });
Output is
{
"added": [
{
"name": "prod2",
"id": 2,
"quantity": 3
}
],
"updated": [
{
"name": "prod1",
"id": 1,
"quantity": 3
}
],
"removed": [
{
"name": "prod3",
"id": 3,
"quantity": 4
}
]
}
If you want do find matching objects in both array using their productId,
Here is my solution
First get the productId list from one array then you can easily filter the another array using includes() method
let listSelectedProducts = [{name: "prod2", productId: 1}, {name: "prod2", productId: 2}, {name: "prod2", productId: 3}]
let oldProductsArray = [{name: "prod1", productId: 1}, {name: "prod1", productId: 2}]
let oldIds = oldProductsArray.map(d=>d.productId)
let arrayProductToDelete = listSelectedProducts.filter(d=>oldIds.includes(d.productId))
console.log(arrayProductToDelete)

Javascript, filtering array of objects and return all the unique objects

I have an array of objects, that contains properties that are objects:
let allPersons = [
{ id: "abcdefg",
name: "tom",
...
phone: {
brand: "blah"
id: "hijklm"
...
}
},
{ id: ....}, {...}, {...}
];
What I need to do is filter those objects and returning all the phones, filtering them by id so all phones returned are unique.
I tried to retrieve first all the phones:
// allPersons is the full array mentioned above
let phones = [...new Set(allPersons.map(person => person.phone))];
then I tried to return all the unique phones, but unsuccessfully:
let result = phones.map(phone => phone.id).filter((value, index, self) => self.indexOf(value) === index)
This returns only the unique ids of the phones, but I want the entire object. What can I do?
UPDATE:
phone Ids are NOT unique, e.g. nokia3310 has id 1, nokia3330 has id 2, etc: so tom and john can have the same phone and phone ids could be duplicated!
Make an object indexed by IDs instead, then take the object's values:
const phonesById = Object.fromEntries(
allPersons.map(
({ phone }) => [phone.id, phone]
)
);
const uniquePhones = Object.values(phonesById);
If you're trying to get the phone object in each object of the array, then the code below will do that for you.
It gets the phone object and stores it in a
var objArr = [
{id: "abcdefg", name: "tom", phone: {brand: "blah", id: "hijklm"}},
{id: "guidiuqwbd", name: "john", phone: {brand: "hihihih", id: "ayfva"}},
{id: "yuygeve", name: "doe", phone: {brand: "hahahah", id: "cqcqw"}}
]
var allPhoneObjects = [];
objArr.forEach(function(currObj){
var phoneObj = currObj.phone;
allPhoneObjects.push(phoneObj);
});
console.log(allPhoneObjects);
I propose you the following solution
let uniques = new Set();
const phones = allPersons.filter(({phone}) => (
uniques.has(phone.id) ? false : !!uniques.add(phone.id)
)).map(p => p.phone)
Basically, we define a Set to record ids of the phones already processed, and a filter function on the allPersons array, that returns only the phones not already in the Set. We complete with the map to extract only the portion of JSON needed
EDIT
You can use just one function on the allPersons array using the reduce function
let uniques = new Set();
const phones = allPersons.reduce( (filtered, {phone}) => {
if (!uniques.has(phone.id)) {
filtered.push(phone);
uniques.add(phone.id);
}
return filtered
}, [])

Javascript array difference

I have two arrays like so
data = [{id: 1, name: apple},
{id: 2, name: mango},
{id: 3, name: grapes},
{id: 4, name: banana}]
data2 =[{id: 1, name: apple},
{id: 3, name grapes}]
My Expected result would be:
[{ id: 2, name: mango},
{id:4, name: banana}]
My code is
let finalData =[];
data.forEach(result => {
data2.find(datum => {
if(datum['id'] === result['id]{
finalData.push(result);
}
})
})
I am getting wrong result. What is the simplest code or library that I can use?
Your sample data doesn't make sense, but assuming you mean that all data items that have matching IDs also have matching names and also assuming you want a set of all items where the IDs are the same in the two sets of data, you could use a Set to keep track of which IDs are present in one array then filter the second array by those that have their IDs in the set:
const idsInFirst = new Set(data.map(d => d.id));
const intersection = data2.filter(d => idsInFirst.has(d.id));
The reason why an intermediate Set structure is used is because it allows O(1) lookups after a one-time scan, which is more efficient than repeatedly scanning the first array over and over.
If you meant to say you wanted a difference between data sets (items excluded from data that are in data2), you'd want to negate/inverse things a bit:
const idsToExclude = new Set(data2.map(d => d.id));
const difference = data.filter(d => !idsToExclude.has(d.id));
Edit
After your clarifying edit, it's that second block of code that you'll want.
I would say a good way to do that is filtering your longest array using a function that will validate if the object id is present in both arrays. Check this example:
const data = [
{id: 1, name: 'apple'},
{id: 2, name: 'mango'},
{id: 3, name: 'grapes'},
{id: 4, name: 'banana'}
]
const data2 =[
{id: 1, name: 'apple' },
{id: 3, name: 'grapes' }
]
const longest = data.length > data2.length ? data : data2;
const shortest = data.length <= data2.length ? data : data2;
const finalData = longest.filter( obj => !shortest.find( o => o.id === obj.id ) )
console.log(finalData)
Good luck!

Categories