why does ```map``` work on array but ```find``` breaks the code - javascript

I am working in React Native with a variable that has a value equal to an array of objects:
const list2020 = [
{
"monthId": "4",
"monthName": "May",
"firstEkadasi": {
"jsDayOfWeek": "1",
"dayOfWeek": "Monday",
"dayInMonth": "4",
"ekadasiName": "Mohini"
},
"secondEkadasi": {
"jsDayOfWeek": "1",
"dayOfWeek": "Monday",
"dayInMonth": "18",
"ekadasiName": "Apara"
},
"thirdEkadasi": {}
},
{
"monthId": "5",
"monthName": "June",
"firstEkadasi": {
"jsDayOfWeek": "2",
"dayOfWeek": "Tuesday",
"dayInMonth": "2",
"ekadasiName": "Pandava Nirjala"
},
"secondEkadasi": {
"jsDayOfWeek": "3",
"dayOfWeek": "Wednesday",
"dayInMonth": "17",
"ekadasiName": "Yogini"
},
"thirdEkadasi": {}
}
]
I have a function that maps over this array:
function TestingMyCode() {
const getCurrentMonth = new Date().getMonth();
let plusOne = getCurrentMonth + 1
let currentMonthIndex = list2020.findIndex((list2020) => list2020.monthId == getCurrentMonth);
let nextMonthIndex = currentMonthIndex + 1;
return (
list2020.find((list, index, array) => {
if (list.thirdEkadasi.dayOfWeek == undefined) {
return (
<View key={index}>
<Text style={styles.displayEkadasi}>{return-values-of-next-object-in-array (I want to return the values of the next month)}</Text>
</View>
)
}
})
)
}
When I use list2020.map I can get a result (even though it is not the desired result).
However, when I used list2020.find it breaks the app with an error
Objects are not valid as a React Child (found: object with keys {monthID, monthName,...}). If you meant to render a collection of children, use an array instead
Is there a vanilla JavaScript way of fixing this?
My objective is to display the values from the next object in the array (if the current object in array does not meet criteria:
If the current Month and date (which is May 19) is greater than the dates in the object
show the values from the next month (June)
Since the date is after May 18th, my app is not showing any text. I could easily display text with if(data.monthId == month && data.firstEkadasi.dayInMonth >= dayOfMonth) {<Text>display text</Text>}
But I am stuck if the dayInMonth is less than current date. For example, it's May 19 and the object at index 0 has two Ekadasi dates; but both dates are less than 19.
How can I display the values from the next objects in the array?
You can view repo of my code here

The syntax of Array.prototype.find is arr.find(callback(element[, index[, array]])[, thisArg]) and the callback function should return Boolean(true/false).
The find() method returns the value of the first element in the provided array that satisfies the provided testing function.
You should use the filter() method instead of find() to display all values that meet the criteria.
so you should use
return (
list2020.filter((list) => list.thirdEkadasi.dayOfWeek == undefined)
.map((item) =>
<View key={index}>
<Text style={styles.displayEkadasi}>{return-values-of-next-object-in-array (I want to return the values of the next month)}</Text>
</View>);
);
So what you want to do will be like this:
const newList2020 = list2020.map((item) => ({
monthId: item.monthId,
monthName: item.monthName,
ekadasi: [
item.firstEkadasi,
item.secondEkadasi,
item.thirdEkadasi
]
});
const thisMonth = new Date().getMonth();
const todayDate = new Date().getDate();
newList2020.filter(item => (
item.ekadasi.find(ekadasi =>
item.monthId > thisMonth
|| (item.monthId === thisMonth && ekadasi.dayInMonth >= todayDate)
) !== undefined
)).map((item, index) =>
<View key={index}>
<Text style={styles.displayEkadasi}>{return-values-of-next-object-in-array (I want to return the values of the next month)}</Text>
</View>
);

The problem here is FIND expects a callback function, not just an item. In a way, it's very much like FILTER.
The following example is from MDN docs
const inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
const result = inventory.find( ({ name }) => name === 'cherries' );
console.log(result) // { name: 'cherries', quantity: 5 }
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
So you need to rewrite your => line.

Related

Sorting array before adding new state in existing state

I am building a simple blog app in react and I am trying to sort the new array before setting into existing state array and when I try to sort it then It is showing unexpected results and it is not sorting the array the way I am expecting to.
App.js
function Blog() {
const [blogs, setBlogs] = useState([]);
const fetchBlogs = () => {
axios.get("/api/blogs/").then((res) => {
const sortArray = res.data.blogs
sortArray.sort((a, b) => (a.id > b.id) ? 1 : -1)
setBlogs((prev) => {
return prev.concat(sortArray);
});
})
}
return (
<>
{blogs?.map((res) =>
{res.id}
)}
</>
)
}
I am returning last 5 blogs from the database and then after it and then after it.
So First blogs are like :-
[
{
"id": 190,
"title": "Last blog"
},
{
"id": 189,
"title": "Last blog"
},
{
"id": 188,
"title": "Last blog"
},
{
"id": 187,
"title": "Last blog"
},
{
"id": 188,
"title": "Last blog"
},
]
And I am sorting it to show biggest first like 190, 189, 188,...
It is sorting for the first 5 blogs without any problem, but I load more blogs on click then It is sorting like 187, 186, 185, 184, 183, 190, 189, 188... which is unexpected.
I tried using concat method using
response.sort((a, b) => (a.id > b.id) ? 1 : -1)
setBlogs((prev) => {
return prev.concat(sortArray);
});
What I am trying to do?
I am trying to sort the array according to the id with current state and newly fetched state and set into state.
Any help would be much Appreciated. Thank You
Assuming that your are calling fetchBlogs after every scroll, i.e at a time 5 blogs, you are making sure your prev is sorted and your sortArray is sorted. But you are not ensuring that your combination of prev and sortedArray is sorted. Which means you would be concatenating two sorted arrays of size 5 each but the entire 10 elements need not be in sorted order.
i.e fetch first instance arrays of ids 10, 7, 11, 4, 6
it gets sorted to 11,10,7,6,4
Second instance fetch retrieves 45, 3, 56, 2 , 1
it gets sorted to 56, 45, 3, 2, 1
now you can concatenating the two will not work.
Rather than sorting and concatenating, it might make sense to concatenate and then sort the entire entry. Now this might have implications to time/cost as the size grows.
const fetchBlogs = () => {
axios.get("/api/blogs/").then((res) => {
const sortArray = res.data.blogs
setBlogs((prev) => {
var newArray = prev.concat(sortArray);
return newArray.sort((a, b) => (a.id > b.id) ? 1 : -1)
});
})
}

How to transform Javascript Object based on one of the column?

I am really struggling with the Object transformation. I have an array of Object and want to transform it into Highcharts multi line chart input.
For x-axis i need a object of unique dates.
For Y -axis i need a series of objects, each will have equal number of points as unique dates from step 1. For a ID and date if data is available it will add that value otherwise null.
Original:
[{
"date": "1997-09-29",
"Count": 100,
"ID": "AB12-R"
},
{
"date": "1997-12-30",
"Count": 104.7,
"ID": "AB13-R"
},
{
"date": "1997-12-30",
"Count": 1192,
"ID": "BA12-R"
},
{
"date": "1998-03-30",
"Count": 981,
"ID": "BA12-R"
},
{
"date": "1998-06-01",
"Count": 341,
"ID": "BA12-R"
}
]
Output:
[
{
Identiy: 'AB12-R',
data: [100, null, null, null]
},
{
Identiy: 'AB13-R',
data: [null, 104.7, null, null]
}, {
Identiy: 'BA12-R',
data: [null, 1192, 981, 341]
}
]
Explanation:
In Original i have 4 unique dates, Hence the length of data for each identity is 4. Now for each unique date i'll check if there is any entry in Original if yes i'll push it in data if not that null.
My Code:
From some help from, this is what i was trying. But this is not checking the unique dates and just creating the data object length which is equal to number of object in the Original. Here is that implementation https://jsfiddle.net/3u9penko/
const data = [{"date":"1997-09-29","Count":100,"ID":"AB12-R"},{"date":"1997-12-30","Count":104.7,"ID":"AB13-R"},{"date":"1997-12-30","Count":1192,"ID":"BA12-R"},{"date":"1998-03-30","Count":981,"ID":"BA12-R"},{"date":"1998-06-01","Count":341,"ID":"BA12-R"}];
const hcData = [];
data.forEach((d, i) => {
const checkIfExist = hcData.find(data => data.id === d["ID"])
if (checkIfExist) {
checkIfExist.data[i] = d["Count"];
} else {
const initialData = [...Array(data.length)]
initialData.fill(null, 0, data.length)
initialData[i] = d["Count"];
hcData.push({
data: initialData,
id: d["ID"]
})
}
})
console.log(hcData)
Create a Map of unique dates to index using a Set to get the unique dates, and then converting to a Map with the date as key, and the index as value.
Reduce the array to a Map using the ID as the key, and initializing the value with an empty array, that has the length of the dates Map size filled with null. Add the value of the current object to the array of nulls at the index of the date in the dates Map.
Convert the Map to an array of objects using Array.from().
const fn = arr => {
// create a Map of uniqe dates with their index
const dates = new Map(Array.from(
new Set(arr.map(o => o.date)),
(v, i) => [v, i]
))
return Array.from(
arr.reduce((acc, o, i) => {
// if an ID doesn't exist on the Map init it with an empty array of null values (counts)
if(!acc.has(o.ID)) acc.set(o.ID, new Array(dates.size).fill(null))
// add the current ID count to the array of counts
acc.get(o.ID)[dates.get(o.date)] = o.Count
return acc
}, new Map),
([Identity, data]) => ({ Identity, data }) // convert to an array of objects
)
}
const arr = [{"date":"1997-09-29","Count":100,"ID":"AB12-R"},{"date":"1997-12-30","Count":104.7,"ID":"AB13-R"},{"date":"1997-12-30","Count":1192,"ID":"BA12-R"},{"date":"1998-03-30","Count":981,"ID":"BA12-R"},{"date":"1998-06-01","Count":341,"ID":"BA12-R"}]
const result = fn(arr)
console.log(result)

How to reformat a JSON array into another format "grouping" based on different keys

Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good

Merge objects with corresponding key values from two different arrays of objects

I've got two arrays that have multiple objects
[
{
"name":"paul",
"employee_id":"8"
}
]
[
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
}
]
How can I achieve the following with either ES6 or Lodash?
[
{
"name":"paul",
"employee_id":"8"
"data": {
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
}
]
I can merge but I'm not sure how to create a new child object and merge that in.
Code I've tried:
school_data = _.map(array1, function(obj) {
return _.merge(obj, _.find(array2, {employee_id: obj.e_id}))
})
This merges to a top level array like so (which is not what I want):
{
"name":"paul",
"employee_id":"8"
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
The connector between these two is "employee_id" and "e_id".
It's imperative that it's taken into account that they could be 1000 objects in each array, and that the only way to match these objects up is by "employee_id" and "e_id".
In order to match up employee_id and e_id you should iterate through the first array and create an object keyed to employee_id. Then you can iterate though the second array and add the data to the particular id in question. Here's an example with an extra item added to each array:
let arr1 = [
{
"name":"mark",
"employee_id":"6"
},
{
"name":"paul",
"employee_id":"8"
}
]
let arr2 = [
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
},
{
"years_at_school": 12,
"department":"Arr",
"e_id":"6"
}
]
// empObj will be keyed to item.employee_id
let empObj = arr1.reduce((obj, item) => {
obj[item.employee_id] = item
return obj
}, {})
// now lookup up id and add data for each object in arr2
arr2.forEach(item=>
empObj[item.e_id].data = item
)
// The values of the object will be an array of your data
let merged = Object.values(empObj)
console.log(merged)
If you perform two nested O(n) loops (map+find), you'll end up with O(n^2) performance. A typical alternative is to create intermediate indexed structures so the whole thing is O(n). A functional approach with lodash:
const _ = require('lodash');
const dataByEmployeeId = _(array2).keyBy('e_id');
const result = array1.map(o => ({...o, data: dataByEmployeeId.get(o.employee_id)}));
Hope this help you:
var mainData = [{
name: "paul",
employee_id: "8"
}];
var secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
var finalData = mainData.map(function(person, index) {
person.data = secondaryData[index];
return person;
});
Sorry, I've also fixed a missing coma in the second object and changed some other stuff.
With latest Ecmascript versions:
const mainData = [{
name: "paul",
employee_id: "8"
}];
const secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
// Be careful with spread operator over objects.. it lacks of browser support yet! ..but works fine on latest Chrome version for example (69.0)
const finalData = mainData.map((person, index) => ({ ...person, data: secondaryData[index] }));
Your question suggests that both arrays will always have the same size. It also suggests that you want to put the contents of array2 within the field data of the elements with the same index in array1. If those assumptions are correct, then:
// Array that will receive the extra data
const teachers = [
{ name: "Paul", employee_id: 8 },
{ name: "Mariah", employee_id: 10 }
];
// Array with the additional data
const extraData = [
{ years_at_school: 6, department: "Mathematics", e_id: 8 },
{ years_at_school: 8, department: "Biology", e_id: 10 },
];
// Array.map will iterate through all indices, and gives both the
const merged = teachers.map((teacher, index) => Object.assign({ data: extraData[index] }, teacher));
However, if you want the data to be added to the employee with an "id" matching in both arrays, you need to do the following:
// Create a function to obtain the employee from an ID
const findEmployee = id => extraData.filter(entry => entry.e_id == id);
merged = teachers.map(teacher => {
const employeeData = findEmployee(teacher.employee_id);
if (employeeData.length === 0) {
// Employee not found
throw new Error("Data inconsistency");
}
if (employeeData.length > 1) {
// More than one employee found
throw new Error("Data inconsistency");
}
return Object.assign({ data: employeeData[0] }, teacher);
});
A slightly different approach just using vanilla js map with a loop to match the employee ids and add the data from the second array to the matching object from the first array. My guess is that the answer from #MarkMeyer is probably faster.
const arr1 = [{ "name": "paul", "employee_id": "8" }];
const arr2 = [{ "years_at_school": 6, "department": "Mathematics", "e_id": "8" }];
const results = arr1.map((obj1) => {
for (const obj2 of arr2) {
if (obj2.e_id === obj1.employee_id) {
obj1.data = obj2;
break;
}
}
return obj1;
});
console.log(results);

update/merge array values in React Redux store correctly without duplicates

My initial state is like below and if new Book added or price is changed then new updated array is coming from service whose result i need to merge in my initial state.
const initialState = {
booksData: [
{"Code":"BK01","price":"5"},
{"code":"BK02","price":"30"},
{"code":"BK03","price":"332"},
{"code":"BK04","price":"123"}
]
};
Updated array from server with few records updated/new
data: [
{"Code":"BK01","price":"10"},
{"code":"BK02","price":"25"},
{"code":"BK05","price":"100"}
]
updated state should become after merging updated array with old array.
booksData: [
{"Code":"BK01","price":"10"},
{"code":"BK02","price":"25"},
{"code":"BK03","price":"332"},
{"code":"BK04","price":"123"},
{"code":"BK05","price":"100"}
]
I would filter out elements of the old data that are in the new data, and concat.
const oldBooks = booksData.filter(book => !newData.some(newBook => newBook.code === book.code));
return oldBooks.concat(newData);
Keep in mind you must NOT push values into the old array. In your reducer you MUST create new instances, here a new array. 'concat' does that.
You can first merge both the array together and then reduce it to remove duplicates like
var booksData = [
{"code":"BK01","price":"5"},
{"code":"BK02","price":"30"},
{"code":"BK03","price":"332"},
{"code":"BK04","price":"123"}
]
var newData = [
{"code":"BK01","price":"10"},
{"code":"BK02","price":"25"},
{"code":"BK05","price":"100"}
]
const result = [...newData, ...booksData].reduce((res, data, index, arr) => {
if (res.findIndex(book => book.code === data.code ) < 0) {
res.push(data);
}
return res;
}, [])
console.log(result);
Merge the two array and filter using 'Code' property
const initialState = {
booksData: [
{ "Code": "BK01", "price": "5" },
{ "code": "BK02", "price": "30" },
{ "code": "BK03", "price": "332" },
{ "code": "BK04", "price": "123" }
]
};
const data =
[
{ "Code": "BK01", "price": "10" },
{ "code": "BK02", "price": "25" },
{ "code": "BK05", "price": "100" }
]
let newState = [...initialState.booksData, ...data];
newState = newState.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj['Code']).indexOf(obj['Code']) !== pos;
});
console.log(newState);
Collection of Objects
Filter a merged array to pick only non-existent items by iterating every item in the merged array which its index is before the current index of the "parent" filter iterator
const mergedUnique = [
...[{id:1}, {id:2}, {id:3}],
...[{id:1}, {id:4}, {id:2}]
]
.filter((item, idx, arr) =>
!arr.some(({id}, subIdx) => subIdx < idx && id == item.id)
)
console.log( mergedUnique )
Basic technique for "simple" arrays
Merge some arrays and filter them to pick only non-existent items by checking if the same item exists anywhere before the current item's index in the merged array.
lastIndexOf is used to check backwards, if the current value exists already, which contributes to keeping the order of the merged array in a certain way which might be desirable, which can only be achieved by checking backward and not forward.
Skip checking the first item - is obviously not a duplicate.
const mergedUniqe = [...[1,2,3], ...[1,3,4,5,2]] // [1, 2, 3, 1, 3, 4, 5, 2]
.filter((item, idx, arr) =>
!~arr.lastIndexOf(item, idx-1) || !idx
)
console.log( mergedUniqe )

Categories