Updating state when state is an array of objects - javascript

I have a form with 3 input fields as sender, receiver, and message, form is inside for loop with a unique id:
now I want to update the state when entering form fields , my expected state like below
[
{id: 1, sender: "", receiver: "", message: ""},
{id: 2, sender: "", receiver: "", message: ""},
{id: 3, sender: "", receiver: "", message: ""},
]
I tried the below code,
const [giftMessages, setGiftMessages] = useState([
{ id: '', sender: '' , receiver: '', giftmsg: '' }
]);
const handleGiftMessages = (e, orderItemId) => {
const updateMessages = [
...giftMessages,
{
id : orderItemId,
[e.target.name]: e.target.value
}
];
setGiftMessages(updateMessages);
}
but it's not coming my expected state format

Try below
const arr1 = [{ oid: 11 }, { oid: 12 }, { oid: 13 }];
const [giftMessages, setGiftMessages] = useState([]);
const handleGiftMessages = (e, orderItemId) => {
setGiftMessages(prevGiftMessages => {
const existingItem = prevGiftMessages.find(item => item.id === orderItemId);
return (!existingItem
? [
...prevGiftMessages,
{ id: orderItemId, sender: "", receiver: "", giftmsg: "" }
]
: prevGiftMessages
).map(giftMessage => {
if (giftMessage.id === orderItemId) {
return {
...giftMessage,
id: orderItemId,
[e.target.name]: e.target.value
};
}
return giftMessage;
});
});
};
Code sandbox => https://stackblitz.com/edit/react-val2gy?file=src%2FApp.js

Related

Merge array of objects within array of objects

I have the following objects
Person {
name: string
birthday: Date
lifeEvents: LifeEvent[]
}
LifeEvent {
eventId: number
message: string
comments: string
}
As the data comes in chunks, I will have an array of Person with one object that has name and birthday with values but lifeEvents is empty (Think of this one like a parent object.)
All other objects won't have birthday and name populated and will have only one LifeEvent with either eventId and message with values or eventId and comments
Within that array, I need to get the parent object, which has name and birthday with values, then get all lifeEvents from the remaining objects, merge all items that contains same eventId into a LifeEvent then push it to lifeEvents of the parent.
I have tried array.reduce, array.map but can't figure out a way of combining those objects into one.
My output should be only one Person with all lifeEvents merged by eventId
Sample data:
let results = [
{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: 'First event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: 'Second event',
comments: null
}
]
},
{
name: null
birthday: null
lifeEvents: [
{
eventId: 1,
message: null,
comments: 'First event comment'
}
]
},
{
name: null
birthday: null
lifeEvents: [
{
eventId: 2,
message: null,
comments: 'Second event comment'
}
]
},
]
Appreciate any help.
Premise: The data structure you are using is wrong, you should try to use arrays with homogenous models.
That said I used a reduce method, with a condition to treat the first element in a different way from the other ones.
Last thing, you said merge lifeEvents, I assume you meant to overwrite the nullish values for events with same ids, if you want to overwrite all values then you can omit the merge utility function I wrote.
let results = [{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null,
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 1,
message: 'First event',
comments: null,
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 2,
message: 'Second event',
comments: null,
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 1,
message: null,
comments: 'First event comment',
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 2,
message: null,
comments: 'Second event comment',
}, ],
},
];
const merge = (o1, o2) => {
const r = {...o1}
Object.keys(o1).forEach(k => r[k] = o2[k] || o1[k])
return r
}
const r = results.reduce((o, curr, i) => {
if (i === 0) {
return { ...o,
lifeEvents: []
};
} else {
const currentEvent = curr.lifeEvents[0]
const idx = o.lifeEvents.findIndex((_o) => _o.eventId === currentEvent.eventId);
if (idx !== -1) return { ...o,
lifeEvents: o.lifeEvents.map((_o, i) => i === idx ? merge(_o, currentEvent) : _o)
}
else return { ...o,
lifeEvents: [...o.lifeEvents, currentEvent]
}
}
}, results[0]);
console.log("RESULT:", r);
The following produces the requested result based on examples provided:
let results = [
{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: 'First event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: 'Second event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: null,
comments: 'First event comment'
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: null,
comments: 'Second event comment'
}
]
},
];
// extract parent
const parentResult = results.find((result) => result.name);
// generate unique events from sibling entities
const uniqueEvents = new Map();
results.forEach((result) => result.lifeEvents?.forEach(
(lifeEvent) => {
if (uniqueEvents.has(lifeEvent.eventId)) {
updateEvent(lifeEvent);
} else {
uniqueEvents.set(lifeEvent.eventId, { eventId: lifeEvent.eventId, message: lifeEvent.message, comments: lifeEvent.comments});
}
})
);
// function to update event that is already stored in uniqueEvents
function updateEvent(lifeEvent) {
const existingLifeEvent = uniqueEvents.get(lifeEvent.eventId);
if (lifeEvent.message) existingLifeEvent.message = lifeEvent.message;
if (lifeEvent.comments) {
if (existingLifeEvent.comments) {
existingLifeEvent.comments.concat(lifeEvent.comments)
} else {
existingLifeEvent.comments = lifeEvent.comments;
}
}
}
// populate lifeEvents inside the parentResult
parentResult.lifeEvents = [];
uniqueEvents.forEach((uniqueId) => {
parentResult.lifeEvents.push(uniqueId);
});
console.log(parentResult);

JavaScript string search for array of objects

I need to implement a search function for a table.
I got an array of objects with unnecessary object properties.
I need to map the array to get necessary properties and then do the filtration.
This is my code.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
const tableColumns = ['name', 'country.name'];
const onSearch = (e) => {
e = e.toLowerCase();
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
if (e) {
const result = mappedItems.filter((item) => {
const str = JSON.stringify(item).toLowerCase();
if (str.search(e) >= 0) return item;
});
return result;
} else {
return mappedItems;
}
};
console.log(onSearch('GERMANY'));
In an item object, I only need to get these two fields
const tableColumns = ['name', 'country.name'];
But this only gives me the name property
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
My first question is how to map to expect a result like this
{
name: 'pathum',
country: {
name: 'SL',
},
},
Second question is JSON.stringtfy map whole object. So If I search "name" it will return all the objects becasue "name" is there in the all records in the stringtify string.
How do I avoid keys in the object when doing the stringify?
Hope my question is clear to you all.
How do I modify this code to get that expected functionality?
const tableColumns = ['name', 'country'];
const deleteProp = ['code'];
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
console.log(key);
if (!tableColumns.includes(key)) delete item[key];
if(key == 'country') delete item[key][deleteProp[0]];
});
return item;
});
This may answer your first question.
You can check if the object has any of the tableColumns paths which includes the searched text. And then get a subset of the filtered objects and only include the tableColumns properties
const items=[{name:"pathum",id:1,status:true,createdAt:"KKKK",country:{name:"SL",code:12,},},{name:"kasun",id:1,status:true,createdAt:"KKKK",country:{name:"USA",code:23,},},{name:"hansi",id:1,status:true,createdAt:"KKKK",country:{name:"GERMANY",code:34}}],
tableColumns = ['name', 'country.name'];
function onSearch(array, e) {
const output = [];
for (const o of array) {
const hasProp = tableColumns.some(path => getProperty(o, path).includes(e))
if (hasProp)
output.push(subSet(o, tableColumns))
}
return output
}
function getProperty(o, path) {
return path.split('.').reduce((acc, p) => acc?.[p], o) || ''
}
function subSet(o, paths) {
const output = {}
for (const path of paths) {
let keys = path.split('.'),
last = keys.pop(),
value = o;
const final = keys.reduce((acc, k) => {
value = value?.[k]
return acc[k] ||= {}
}, output);
final[last] = value?.[last];
}
return output;
}
console.log(onSearch(items, 'pat'));
console.log(onSearch(items, 'kasun'));
First, don't change data. You can clone the data and change it.
And, search should be search. Don't put the data formation in it.
Let's start.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
// We will use object to get the fields you want. To reuse, you can add more fields you want.
const tableColumns = {
// id: 1,
name: 1,
country: {
name: 1
}
}
// getting the mapped items
const mappedItems = items.map((item) => {
const temp = {};
Object.keys(item).forEach((key) => {
const target = tableColumns[key];
if (target) {
if (typeof target === 'number'){
temp[key] = item[key];
} else {
temp[key] = {};
Object.keys(target).forEach(subKey => temp[key][subKey] = item[key][subKey]);
}
}
});
return temp;
});
// search function, use local varibles
const onSearch = (array, countryName) => {
return array.find(element => element.country.name.toLowerCase() === countryName.toLowerCase())
}
const searchResult = onSearch(mappedItems, 'germany');
console.log(searchResult);
You can just create a new array using Array.map
const items = [{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
let minItems = items.map(function(item) {
return {
"name": item.name,
"country": {
"name": item.country.name
}
}
});
console.log(minItems);

Subtract one object from another one with in array

I want to know the efficient way to subtract two object key[amount] values based on the action key.
data = [
{ _id: { token: 'BEEF', action: 'received' }, amount: 4 },
{ _id: { token: 'BEEF', action: 'sent' }, amount: 2 },
{ _id: { token: 'GFUN', action: 'received' }, amount: 9},
{ _id: { token: 'HOT', action: 'received' }, amount: 6 },
{ _id: { token: 'HOT', action: 'sent' }, amount: 4 },
{ _id: { token: 'LINK', action: 'received' }, amount: 8},
{ _id: { token: 'METM', action: 'sent' }, amount: 7 },
{ _id: { token: 'METM', action: 'received' }, amount: 9},
{ _id: { token: 'ORTC', action: 'received' }, amount: 5},
{ _id: { token: 'ORTC', action: 'sent' }, amount: 3 }
]
desired result after calculation
[
{ token: 'BEEF', amount: 2 },
{ token: 'GFUN', amount: 9},
{ token: 'HOT' , amount: 2 },
{ token: 'LINK', amount: 8},
{ token: 'METM', amount: 2 },
{ token: 'ORTC', amount: 2},
]
Maybe something like this:
const results = [];
const idx = {};
data.forEach(v => {
const token = v['_id']['token'];
if(!token in idx) {
const item = {
token: token,
amount: 0
};
results.push(item);
idx[token] = item;
}
idx[token].amount +=
v['_id']['action'] == 'received' ? v.amount : -v.amount;
});
results is the final array. idx is a lookup into the array in order to make locating the correct item easier.
There's lots of ways to approach this, but here's what I did.
First break the array into two arrays, one for the sent items and one for the received.
const sent = data.filter(o => o._id.action === 'sent');
const recieved = data.filter(o => o._id.action === 'received');
I'm assuming that there is always an entry in received and there may or may not be an entry in sent. So I mapped from the recieved array to the results. For each entry, I find the corresponding entry in sent, and if there is no matching entry, I subtract 0.
const differences = recieved.map( r => {
const s = sent.find(o => o._id.token === r._id.token );
const sentAmount = s ? s.amount : 0;
return {
token: r._id.token,
amount: r.amount - sentAmount,
}
})

Why is my logical comparison of the same number returning false?

In the following function, I am comparing item.id to ID, "type of" shows that they are both strings but the comparison is failing. 1234 === 1234 returns false, for instance.
exports.handler = async (event) => {
if (!event.pathParameters || !event.pathParameters.ID) {
return Responses._400({ message: "missing ID" });
}
console.log("data ", data);
let ID = event.pathParameters.ID;
data.find((item) => {
console.log("item.id, ID", item.id, ID);
if (item.id.trim() === ID.trim()) {
return Responses._200(item);
}
});
return Responses._400({ message: "id not found", ID });
}
This is the array of data:
const data = [
{
id: "1234",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
{
id: "5678",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
];
The call to the function originates from the browser as follows:
https://hs40xxxxxx.execute-api.us-east-1.amazonaws.com/dev/get-user/1234
You are using find and return in the wrong way.
See what gets returned with your current approach:
const data = [
{
id: "1234",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
{
id: "5678",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
];
const ID = "1234";
const test = () => {
data.find((item) => {
console.log("item.id, ID", item.id, ID);
if (item.id.trim() === ID.trim()) {
return 200;
}
});
}
console.log(test());
It's undefined.
Instead, you should do this:
const data = [
{
id: "1234",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
{
id: "5678",
email: "user1#user1.com",
username: "user1",
createdAt: "2020-09-10T14:03:09.203Z",
__v: 0,
},
];
const ID = "1234";
const test = () => {
const found = data.find((item) => {
console.log("item.id, ID", item.id, ID);
return item.id.trim() === ID.trim();
});
if (found) {
return 200;
}
}
console.log(test());
Now you're returning status 200 from the outer function, not inside of the find function.
You can read more about how find works here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Essentially, inside of find() you should return false if it's not a match and true if it's a match.
from what I see you are not using find to return a value that qualifies itself rather you impose your own return there, find method actually returns the first element in the array that passes the condition so use the condition with return and try it the other way.
Responses._200(data.find(item => {
console.log("item.id, ID", item.id, ID);
return item.id.trim() === ID.trim()
}));
or store the value of data.find in a value and send a response in another way.

Search object in array and update if exist or add

This is what my array of logged in users looks like:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
}];
I will concat this table when a new user logs in to my system:
this.connectedUsers = [
...this.connectedUsers,
{
...payload,
id: client.id
},
];
Then my array looks like this: (I give this to better understand)
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
}
];
If the user with the uuid like 663-dda updates his point, I perform this method again.
When I leave it as it is, something like this will be done:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
I want to write a very nice (use ES6+) algorithm that first checks if such an object exists in this array (check by id or by user.uuid). If so, update. If not, add a new object. So it should be like this:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
In the code where you're updating the array when a user logs in, you could do like this:
if (!this.connectedUsers.find(user => user.user.uuid === payload.user.uuid) {
this.connectedUsers = [
...this.connectedUsers,
{ ...payload, id: client.id },
];
}
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}];
//let take some sample object
const newObDuplicateUuid = {
user: {
uuid: '663-dda',
points: 6,
},
id: "3333290"
}
const newObDuplicateId = {
user: {
uuid: '756-dda',
points: 6,
},
id: "33332"
}
const newObFresh = {
user: {
uuid: '756-dda',
points: 6,
},
id: "3333290"
}
let checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateUuid.user.uuid && item.id != newObDuplicateUuid.id)
//return false for same uuid
checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateId.user.uuid && item.id != newObDuplicateId.id)
//return false for same id
checkRule = connectedUsers.every(item => item.user.uuid != newObFresh.user.uuid && item.id != newObFresh.id)
//return true
console.log('Passed validation :'+checkRule);
const result = checkRule ? [...connectedUsers,newObFresh] : 'There is duplicate value';
console.log(result);
This is my approach. One function that covers both cases. Just pass the array and the new entry and it will return the updated list
function updateOrAddUser(listOfUsers, newEntry) {
let found = false;
const updatedUserList = listOfUsers.map(entry => {
if (entry.user.uuid === newEntry.user.uuid) {
found = true;
return newEntry;
}
return entry;
});
if (!found) {
updatedUserList.push(newUser);
}
return updatedUserList;
}

Categories