Javascript - Best way to create array with unique elements from another array - javascript

I'll try my best to explain my problem. In my application I have a section that lists all users with a name and image. When adding a new user, the profile picture is taken from an array of default pictures and it has to be different from image of the other users.
Down below there is my solution and it seems to work, but im searching for a cleaner way to do it.
Thank you!
const profileImages = [img1, img2, img3, img4];
let users = [
{
name: "Username1",
image: img1
},
{
name: "Username2",
image: img2
}
];
/*
This array will fill up with the images not already taken by other users,
and I'll randomly pick from these to assign it to the new user
*/
let availableImages = [];
users.forEach(user =>{
if (availableImages.length === 0)
{
availableImages = profileImages.filter(image => image !== user.image);
}
else
{
availableImages = availableImages.filter(image => image !== user.image);
}
});

Use the Array.every() method to check if an image is not used by any user.
let availableImages = profileImages.filter(image => users.every(u => u.image != image));

something like this can work too, but this is a very simplistic approach. you may have some edge cases depending on how these images are assigned if coming from the server-side or something.
// assuming you want to keep a reference to the full list of images
const allImages = [...];
let availableImages = [...allImages];
function getImage() {
const [newImage, ...rest] = availableImages;
availableImages = rest;
return newImage;
}
// use getImage(); when you want to assign a new image

The following code gets you the remaining elements without a second loop. However, programmers must be cautioned that splice() is an expensive operation and it also alters the original array:
profileImages = ['img1', 'img2', 'img3', 'img4'];
users = [
{
name: "Username1",
image: 'img1'
},
{
name: "Username2",
image: 'img2'
}
];
users.map(item => {
let i = profileImages.indexOf(item.image); // get the index
profileImages.splice(i,1); // delete it
});
profileImages; // ['img3', 'img4']

Related

Catching and Altering API values in React Function

Currently I figured out how to call the YGOPRO API in a React function and paste the data inside a stateless component. But now I've come at an impasse when trying to figure out how to use some of the API data to run some float numbers through a process of generating new values to post instead.
Here's the full scenario. I originally made this exact thing in a basic HTML layout because that's what the official YGO forbidden/limited webpage used and what I edited to make a joke video about the f/l list months ago. it still works fairly well and the API displays just fine, but its the matter of using this forloop and math junction code to work in a React environment
This is the getJSON part of the code from the initial HTML project:
$.getJSON(
endpoint + "?startprice=" + startprice + "&endprice=" + endprice,
function (json) {
$.each(json.data, function (ix, obj) {
let cards = [];
let name = obj.name;
let type = obj.type;
let card_sets = obj.card_sets;
let price_array = [];
if (card_sets === undefined) {
return true;
}
for (let i = 0; i < card_sets.length; i++) {
let set_price = parseFloat(card_sets[i].set_price);
if (set_price === 0 || set_price === null || set_price === "0.00") {
continue;
} else {
price_array.push(set_price);
}
}
let min_price = Math.min(...price_array);
let max_price = Math.max(...price_array);
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
});
let min_price_usd = formatter.format(min_price);
let max_price_usd = formatter.format(max_price);
console.log(name);
console.log(min_price);
What this originally did was simply read each monster entry in the API call within the price values described and grabbed each price value from each set listed within each entry while returning true to any part of the entry that didn't have a valid price value. it would then put all the valid money values in a temp array for that monster entry, use the Math function to get both a minimum and maximum price value from that API and then format them to appear as USD before placing them in the front-end of their respective monster entry.
This is the section of the React function that calls and collects the data for the API, including some throwaway lines that I left in after trying to figure all this out:
export default function TestView() {
// for the Banned cards, or all cards more than $5. The limited section for cards more than $1 will come later
// const name = "Dark Magician";
const startprice = 5.0;
const endprice = 99999.99;
const [data, setData] = useState([]);
// how to apply api names to these values?
let name = data.name;
let type = data.type;
console.log(name);
let price_array = [];
useEffect(() => {
getCardDataByPrice(startprice, endprice)
.then(({ data }) => {
setData(data.data);
})
.catch((error) => console.error(`Error: ${error}`));
}, []);
function getCardDataByPrice(startprice, endprice) {
const ygoproURL = "https://db.ygoprodeck.com/api/v7/cardinfo.php";
let ygoproEndpoint = `${ygoproURL}?startprice=${startprice}&endprice=${endprice}`;
if (startprice) {
ygoproEndpoint += `&startprice=${startprice}`;
}
if (endprice) {
ygoproEndpoint += `&endprice=${endprice}`;
}
return axios.get(ygoproEndpoint);
}
// most of the code used to convert money values of each api entry
// This is where the function that grabs the API values for card set prices was to be gathered and calculated like in the original HTML.
let min_price = Math.min(...price_array);
let max_price = Math.max(...price_array);
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
});
let min_price_usd = formatter.format(min_price);
let max_price_usd = formatter.format(max_price);
console.log(name);
console.log(min_price);
return (...
...etc.
What I am trying to figure out is how I could grab that API data for each monster entry and start grabbing the cash value from each set like how I used to do it here. I'm assuming its within the function that would call the Endpoint in the first place, but I'm not sure.
tl;dr: trying to port API call code from an HTML site into React is being hampered by not knowing where I can grab and alter the data to stick it to the front-end.
You can find the original HTML source code here to help you understand what I'm trying to do.
https://github.com/patrickfinnigan/yugioh_banlist_manipulation
And you can also find how I'm trying to do the same thing in React, in case you'd need to see more of the overall code.
https://github.com/patrickfinnigan/custom_yugioh_banlists_react
(The Relative path would be 'ygo_banlists\src\views\TestView.js')

Is it efficient to create multiple keys pointing to the same object?

I have an array of countries, where each country have three properties.
const countries = [
{ name: "Poland", alpha: "POL", code: "616" },
{ name: "Hungary", alpha: "HUN", code: "348" },
// and so on...
]
Later on I want to easily access each country by any of these properties.
I was thinking of reducing this array to an object that would have three keys for each country pointing to the very same country object.
Something like this:
const countriesObject = countries.reduce((object, country) => {
const { name, alpha, code } = country;
// Create individual country object
object[name] = { ...country };
// Reference just created country object
object[code] = object[name];
object[alpha] = object[name];
return object;
});
In the end I could access each country object either by its name, code or alpha.
countriesObject["Poland"] // →
countriesObject["POL"] // → The same object
countriesObject["616"] // →
My question is, would it be considered good practice, or there are some better ways to achieve the same or similar result?
Thank you!
That's fine, as all of those keys, as you correctly noted, will be pointing to the same object. The biggest problem that I see here is that it's easy to reduce readability of the code by using this approach. Let's say we have this fragment:
console.log( countriesObject[id] );
The question is, what is id here? Is it full country name? or just alpha? or code? You might just not care, true, but if you do, consider giving them additional structure:
const countriesObject = countries.reduce((object, country) => {
const { name, alpha, code } = country;
const countryCopy = { ...country };
// it makes sense to place them on the same line to show the intent
object.name[name] = object.code[code] = object.alpha[alpha] = countryCopy;
return object;
}, { name: {}, code: {}, alpha: {} });
Another potential issue is that you won't be able to drop the countries easily from this object; it's not enough to delete just a single key pointing to it, you'll have to go and remove all three. But that doesn't seem to be a big thing here; this looks more like a dictionary.
You can indeed write it like this:
var countries = {[
"poland": {
alpha: "POL", code: "616"
},
"hungary": {
alpha: "HUN", code: "348"
}
]}
Accessing each country like this:
var poland = countries.poland;
This, in turn, produces more readable code:
var alpha = countries.poland.alpha;
instead of
var alpha = countries[0].alpha;
But there is no set preference.
Docs

How to use multiple Nodelists with forEach method

I'm working on a project that is basically a e-store, with a bin. What I'm trying to do is send a POST request to the server with my items from the bin.
I have an empty array. I also have 3 Nodelists, I want to reach their values, textContent, dataset and nest all of them in my empty array.
This is the array:
var products = { Lines: [ ] };
What I've tried so far:
const prName = this.parentNode.parentNode.querySelectorAll('.product-name-link');
const prInput = this.parentNode.parentNode.querySelectorAll('.product-quantity-input');
const prPrice = this.parentNode.parentNode.querySelectorAll('.product-price-total');
prName.forEach(product => products.push(product.dataset.id))
prInput.forEach(product => products.Lines.push(product.value))
prPrice.forEach(product => products.Lines.push(product.textContent))
I want them to have a key=> value like in the example above.
The result that I need to get looks similar to this:
"Lines":[
{
"Assortiment":"1627aea5-8e0a-4371-9022-9b504344e724",
"Quantity":12678967.543233,
"SalePrice":12678967.543233,
"Warehouse":"1627aea5-8e0a-4371-9022-9b504344e724"
},
{
"Assortiment":"1627aea5-8e0a-4371-9022-9b504344e724",
"Quantity":12678967.543233,
"SalePrice":12678967.543233,
"Warehouse":"1627aea5-8e0a-4371-9022-9b504344e724"
},
{
"Assortiment":"1627aea5-8e0a-4371-9022-9b504344e724",
"Quantity":12678967.543233,
"SalePrice":12678967.543233,
"Warehouse":"1627aea5-8e0a-4371-9022-9b504344e724"
}
],
... with everything already said in my comments, a working solution might look like that ...
const products = { Lines: [] };
const prName = this.parentNode.parentNode.querySelectorAll('.product-name-link');
const prInput = this.parentNode.parentNode.querySelectorAll('.product-quantity-input');
const prPrice = this.parentNode.parentNode.querySelectorAll('.product-price-total');
// create a product item with each iteration step ...
prName.forEach((elmNode, idx) => products.Lines.push({
Assortiment: elmNode.dataset.id,
Quantity: prInput.item(idx).value,
SalePrice: prPrice.item(idx).textContent
}));
Wrap them with Array.from() as follows:
Array.from(elements).forEach( function(el) {
console.log(el);
});

How can I get specific keys from this?

I've used hasOwnProperty and typeof in the past but this one is stumping me...
I'm trying to get all the keys that have keys that match so I can pair them with other keys example:
{"meals": [{
strIngredient1 : lemons
strIngredient2 : paprika
strIngredient3 : red onions
strIngredient4 : chicken thighs
strIngredient5 : vegetable oil
strMeasure1 : 2 Juice
strMeasure2 : 4 tsp
strMeasure3 : 2 finely chopped
strMeasure4 : 16 skinnless
strMeasure5 :
}]}
It's apparent that strIngredient1 matches with strMeasure1 etc...
Any suggestions or help would be greatly appreciated!!
Explained
In this example, you can see that I've provided the solution in two parts, one being a simple way to simply access 'x' ingredient from the array of meals, then another solution which will iterate over the array of meals, printing out each individual ingredient.
As I've stated within my solution, you can use forEach or alternatively, you can also use functions such as map or reduce if you wish. In the event that you don't know when to use which, the basic rule of thumb is that you'd use map or reduce if you wish to follow functional programming concepts. The forEach solution allows for side effects to happen more easily, etc... I mean this is debatable to a certain extent, but that's the basic idea anyways...
Edit
I've included a simple log function just for this demo, long story short, when you run this code snippet, personally I find it disgusting how little space is provided for the console window, so log one thing at a time after some delay and clear the console too.
let delay = 0;
const DELAY_INC = 1500;
// Just for this demo, have the ability to log something,
// after a delay and clear the console.
const log = (arg, alrt) => {
setTimeout(() => {
console.clear();
console.log(arg);
if (alrt != null) {
alert(alrt);
}
}, delay);
delay += DELAY_INC;
};
// Your data.
var data = {
"meals": [{
strIngredient1: 'lemons',
strIngredient2: 'paprika',
strIngredient3: 'red onions',
strIngredient4: 'chicken thighs',
strIngredient5: 'vegetable oil',
strMeasure1: '2 Juice',
strMeasure2: '4 tsp',
strMeasure3: '2 finely chopped',
strMeasure4: '16 skinnless',
strMeasure5: ''
}]
};
// Just some demo.
var meals = data.meals;
var meal = meals[0];
var ingredient = meal.strIngredient1;
log(data); // Log the raw data.
log(meals); // Log the array of meals.
log(meal); // Log a specific meal.
log(ingredient); // Log a specific ingredient.
// If you wish to iterate, log each ingredient for each meal.
data.meals.forEach(meal => Object.keys(meal).forEach(key => log(meal[key])));
// Here's a solution.
const newArray = data.meals.reduce((array, meal) => {
// Rather than iterate over ALL of the keys, just
// do this, basically 50% of the keys.
const subArray = Object.keys(meal).filter(key => key.indexOf('strIngredient' == -1));
// Basically add some ojects to the array.
subArray.forEach(key => {
const int = key.replace(/\D/g, '');
const measureKey = `strMeasure${int}`;
const ingredientKey = `strIngredient${int}`;
const obj = {
ingredient: meal[ingredientKey],
measure: meal[measureKey]
};
array.push(obj);
});
// Make sure to return the array.
return array;
}, []);
// Now just print the resuts, and make sure that you know
// and alert that the app has finished.
log(newArray, 'FINISHED');
For those interested or if it helps anyone here is the final product! All neat and tidy in one array, easy to use! :) Thank you again JO3-W3B-D3V!
getRecipe: function(url) {
request({
url: url,
method: 'GET'
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
var result = JSON.parse(body);
//console.log(result);
// Just some TESTING.
var meals = result.meals; //returns array
var meal = meals[0]; // returns object
//console.log(meal);
// Start here to rename keys and match them to the ingredients.
const newArray = meals.reduce((array, meal) => {
// Rather than iterate over ALL of the keys, just
// do this, basically 50% of the keys.
const subArray = Object.keys(meal).filter(key => key.indexOf('strIngredient' == -1));
// console.log(subArray);
// Basically add some ojects to the array.
subArray.forEach(key => {
const int = key.replace(/\D/g, '');
const measureKey = `strMeasure${int}`;
const ingredientKey = `strIngredient${int}`;
const obj = {
measure: meal[measureKey],
ingredient: meal[ingredientKey]
};
// console.log(obj); //Testing data before
if (obj.measure && obj.ingredient != 'undefined' || undefined || "" || null){
array.push(obj);
// console.log(array); //Testing data after
}
});
const recipeName = meal.strMeal;
const instruction = meal.strInstructions;
const video = meal.strYoutube;
const thumb = meal.strMealThumb;
const nation = meal.strArea;
const category = meal.strCategory;
const recipe = {recipeName, instruction, video, thumb, nation, category};
array.push(recipe);
//console.log(recipe); Testing full array
// Make sure to return the array.
return array;
}, []);
// Now just print the resuts, and make sure that you know
// and alert that the app has finished.
console.log(newArray, "FINISHED");

Nested foreach/forloops, looping through JSON to extract values

I'm going to pre-face this with saying i'm not sure this is the best approach so other approaches are greatly appreciated
End Goal: To store a list of products and the toppings purchased by calling the woocommerce API and using the response data
I'm calling the woocommerce REST api that provides me a good chunk of JSON data back. In the JSON are line_items. These are the products purchased. Nested in line_items are meta_data, this is the toppings for example tomato or sauce.
Attached an image of the JSON
So what i'm trying to do is create something like this
var testOrderItems =
[{
title: "Fried Chicken Burger",
meta: [
"Lettuce",
"cheese slice",
"kethcup"
]
},
{
title: "Beef Burger",
meta: [
"Lettuce",
"cheese slice",
"kethcup"
]
}
]
which will follow my schema for oder items
var orderItems = new Schema({
title: {type: String, required: true},
meta: [{type: String}]
});
So to do this, i figured I would just do a forloop or foreach through the JSON to get all the product names and their meta. Getting actual values is easy. The hard part is creating the array or JSON object that I can then store, i'm just not sure how to create it whilst in the loop. Below are a few things I tried
let fullData = JSON.parse(result)
//parsed response from woocommerce API call
fullData.line_items.forEach((product, index) => {
//for each line item get me the product
orderItems.push(product.name)
//var namey =
//push the product name to the orderItemsArray
product.meta_data.forEach(function(meta) {
//checks for string as one of the plug-ins fills the meta with more nested information and we only want the top level string
if (typeof meta.value === 'string' || meta.value instanceof String)
// it's a string
orderItems.push(meta.value)
//Onbviously won't nest the meta with the product name just on new lines
})
});
The I thought I could do it in for loops by storing an ID ref as "i" and being able to re-reference this later in the nested loop to add the meta, i got a little lost with this
var length = fullData.line_items.length
for (let i = 0; i < length; i++) {
// console.log(i);
console.log(fullData.line_items[i].name)
for (let j = 0; j < fullData.line_items[i].meta_data.length; j++) {
var metaValue = fullData.line_items[i].meta_data[j].value
if (typeof metaValue === 'string' || metaValue instanceof String) {
console.log(fullData.line_items[i].meta_data[j].value);
stringMeta = fullData.line_items[i].meta_data[j].value
//this works but has drawbacks
//1 obviously just overwrites itself each time
//2 will stop at the end of meta so won't add items without meta
finalOrderItems = {
id: i,
name: fullData.line_items[i].name,
meta: [stringMeta]
}
}
}
}
and thats where I am, feels like this should be incredibly easy but can't quite grasp it at the moment.
You could simply create the object that represents your schema first, then return it from a map of your json Object. So, it would look like the following:
let testOrderItems = fullData.line_items.map((product)=>{
let obj = { name: product.name };
obj.meta = product.meta_data.map((meta)=>{
if (typeof meta.value === 'string' || meta.value instanceof String)
return meta.value;
}).filter((value)=>!!value);
return obj;
})
console.log(testOrderItems);
Although, the if statement seems a little redundant, since the woocommerce api will simply either have meta or not. However, you may have some plugin or something which is adding more information to the meta area so i've kept it in my example.
This looks like a job for map and reduce not forEach. map will map each object of line_items into a new object and reduce will group and organize the metas by key for each object:
var orderItems = fullData.line_items.map(function(product) { // map each product in line_items
return { // into a new object
title: product.name, // with title equals to the current product's name
meta: product.meta_data.reduce(function(acc, meta) { // and metas accumulated from each meta object in the current product's meta_data array
acc[meta.key] = acc[meta.key] || []; // first, check if there is an array for the current meta's key in the group object 'acc', if not create one
acc[meta.key].push(meta.value); // add the current meta's value to that array
return acc;
}, {})
}
});
Shorter using arrow functions:
var orderItems = fullData.line_items.map(product => ({
title: product.name,
meta: product.meta_data.reduce((acc, meta) => {
acc[meta.key] = acc[meta.key] || [];
acc[meta.key].push(meta.value);
return acc;
}, {})
}));

Categories