React Cannot read property 'lastName' of undefined - javascript

I'm trying to merge data from two API in the new API but I'm getting error Uncaught TypeError: Cannot read property 'lastName' of undefined. This is happening because is not finding lastName when it missing in API2. In this case, I want to initialize it as empty string if not able to find that in the merged API.
API 1 look:
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
}
}
API2 look:
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
lastName: 'Second'
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
lastName: 'Second 1'
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
lastName: 'Second 3'
}
}
Merged API look:
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
lastName: 'Second Test' //lastName from API 2 + name from API 1
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
lastName: '' //lastName from API 2 + name from API 1
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
lastName: 'Second 1 Test 2' //lastName from API 2 + name from API 1
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
lastName: 'Second 3 Test 3' //lastName from API 2 + name from API 1
}
Fetched data:
const [api1, setApi1] = useState([]);
const [api2, setApi2] = useState([]);
const [mergeApi, setMergeAPi] = useState([]);
useEffect(() => {
fetch('api1')
.then((response) => {
setApi1(response);
});
});
useEffect(() => {
fetch('api2')
.then((response) => {
setApi2(response);
});
});
useEffect(() => {
const data = api1.map(getData => {
const d = api2.find((object) => {
// return object.name === api1.name;
return object.name === getData.name;
});
return {
...api1,
// name: `${d.name} - ${d.lastName}`
name: d ? `${d.name} - ${d.lastName}` : ""; // solution there
}
});
});

You can use inline || operator:
${d.lastName || ""}
Also you have to change the object here:
return object.name === getData.name; //<----change here.

Try to do this. There could be a case when in your real data could be some name taht not exist in api2.
const data = api1.map(getData => {
const d = api2.find((object) => {
return object.name === getData.name; // <---- NOTE THIS
});
if (!d) { // <---- NOTE THIS
return getData
}
return {
...getData, // <---- NOTE THIS
name: `${d.name} - ${d.lastName}`
}
});
idk if you really have id in objects, but I would compare by id, not by name.
return object.id === getData.id;

useEffect(() => {
const data = api1.map(getData => {
const d = api2.find((object) => {
// return object.name === api1.name;
return object.name === getData.name; //replaced but doesn't work
});
return {
...api1,
name: `${d.name} - ${d.lastName}`
}
});
});
Array.find return undefined if there is no matched item on array. so d could be undefind and therefore code throws error there is no 'lastname' property on d.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
useEffect(() => {
const data = api1.map(getData => {
...
if (d) {
return {
...getData,
name: `${d.name} - ${d.lastName}`
}
} else {
// some code to handle if d is undefined
}
});
});

Try something like this.
1) api1 and api2 data look like object, have Object.values to get array of values.
2) Change to find function. Checking obj2.name === obj1.name
3) check for d value before using.
const api1 = {
0: {
id: 1234,
company: "String",
name: "Test"
},
1: {
id: 2345,
company: "String1",
name: "Test 1"
},
2: {
id: 3456,
company: "String2",
name: "Test 2"
},
3: {
id: 4567,
company: "String3",
name: "Test 3"
}
};
const api2 = {
0: {
id: 1234,
company: "String",
name: "Test",
lastName: "Second"
},
1: {
id: 2345,
company: "String1",
name: "Test 1"
},
2: {
id: 3456,
company: "String2",
name: "Test 2",
lastName: "Second 1"
},
3: {
id: 4567,
company: "String3",
name: "Test 3",
lastName: "Second 3"
}
};
const data = Object.values(api1).map(obj1 => {
const d = Object.values(api2).find(obj2 => obj2.name === obj1.name);
return {
...obj1,
name: d ? `${d.name} - ${d.lastName}` : obj1
};
});
console.log(data);

I have found solution for that:
useEffect(() => {
const data = api1.map(getData => {
const d = api2.find((object) => {
// return object.name === api1.name;
return object.name === getData.name;
});
return {
...api1,
// name: `${d.name} - ${d.lastName}`
name: d ? `${d.name} - ${d.lastName}` : ""; // solution there
}
});
});

Related

Convert array into shorter array

I have array object -
[
{
department:"Reviewer",
name:"John"
},
{
department:"Reviewer",
name:"Carol"
},
{
department:"Reviewer",
name:"June"
},
{
department:"Assistant Reviewer",
name:"Mac"
},
{
department:"Assistant Reviewer",
name:"Issac"
}
]
I want to convert this object into array object as below -
[
{
department:"Reviewer",
name:"John;Carol;June;"
},
{
department:"Assistant Reviewer",
name:"Mac;Issac;"
},
]
I tried using filter -
[... new Set(obj.department)].map((item)=>{return {item.name+";"} });
But this operation is dealing with only one element per item. Its not accumulating names.
You can use reduce to achieve the desired result:
const array = [
{ department: "Reviewer", name: "John" },
{ department: "Reviewer", name: "Carol" },
{ department: "Reviewer", name: "June" },
{ department: "Assistant Reviewer", name: "Mac" },
{ department: "Assistant Reviewer", name: "Issac" }
];
const result = array.reduce((acc, cur) => {
const foundIndex = acc.findIndex(department => department.department === cur.department);
if (foundIndex === -1) {
acc.push({
department: cur.department,
name: cur.name + ';'
});
} else {
acc[foundIndex].name += cur.name + ';';
}
return acc;
}, []);
console.log(result);
Get a set of distinct departments, then map from them to the list of names
const data = [{"department":"Reviewer","name":"John"},{"department":"Reviewer","name":"Carol"},{"department":"Reviewer","name":"June"},{"department":"Assistant Reviewer","name":"Mac"},{"department":"Assistant Reviewer","name":"Issac"}]
console.log([...new Set(data.map(i=>i.department))].map(i=>({
department: i,
name: data.filter(j=>j.department===i).map(j=>j.name).join(';')+';'})))
or, using reduce:
const data = [{"department":"Reviewer","name":"John"},{"department":"Reviewer","name":"Carol"},{"department":"Reviewer","name":"June"},{"department":"Assistant Reviewer","name":"Mac"},{"department":"Assistant Reviewer","name":"Issac"}]
console.log(Object.values(data.reduce((a,{department, name})=>
(((a[department]??={department,name:''}).name+=`${name};`),a),{})))
This would also work:
const input = [ { department: "Reviewer", name: "John", }, { department: "Reviewer", name: "Carol", }, { department: "Reviewer", name: "June", }, { department: "Assistant Reviewer", name: "Mac", }, { department: "Assistant Reviewer", name: "Issac", }, ];
const output = input.reduce((prev, { department, name }) => {
const match = prev.find((item) => item.department === department);
if (match) {
match.name = `${match.name};${name}`;
} else {
prev.push({ department, name });
}
return prev;
}, []);
console.log(output);
Using Array.prototype.reduce() and Array.prototype.find()
Here is a solution using:
.reduce() to build an object that maps from department (aka key) to names (aka value)
.map() to transform that object into an array of objects with desired format
Note that this solution is faster than an array index search (aka other answers here) for large a large input array, at the cost of using a bit more memory.
const array = [
{ department: "Reviewer", name: "John" },
{ department: "Reviewer", name: "Carol" },
{ department: "Reviewer", name: "June" },
{ department: "Assistant Reviewer", name: "Mac" },
{ department: "Assistant Reviewer", name: "Issac" }
];
const result = Object.entries(array.reduce((acc, obj) => {
if(acc[obj.department]) {
acc[obj.department] += obj.name + ';';
} else {
acc[obj.department] = obj.name + ';';
}
return acc;
}, {})).map(arr => ({ department: arr[0], name: arr[1] }));
console.log(result);
This codes should work:
let data = [
{ department: "Reviewer", name: "John" },
{ department: "Reviewer", name: "Carol" },
{ department: "Reviewer", name: "June" },
{ department: "Assistant Reviewer", name: "Mac" },
{ department: "Assistant Reviewer", name: "Issac" }
];
let newData = [];
data.forEach((obj)=>{
if(newData.filter(m => m.department == obj.department).length) {
newData.find(n => n.department == obj.department).name += obj.name + ';';
} else {
newData.push({
department: obj.department,
name: obj.name + ';'
})
}
});
console.log(newData);

How to add value in array which is present inside objects array?

here, I want to update data in such way that if same person with same company comes then it's value of client should come under one array.
input data :
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
Expected output:
[
{
name: 'John',
company: 'abc',
client: ['BMW', 'Mercedes']
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
}
]
Here is a way to do it in O(n) time. If you're fine with O(n^2) time, I'd suggest using reduce (as Mina's answer shows) since it's more straightforward.
const inputs = [{
name: 'John',
company: 'abc',
client: 'BMW'
}, {
name: 'Jack',
company: 'abc',
client: 'Volvo'
}, {
name: 'John',
company: 'abc',
client: 'Mercedes'
}];
const itemsByNameCompany = {};
for (let input of inputs) {
const nameCompany = `${input.name},${input.company}`;
itemsByNameCompany[nameCompany] = itemsByNameCompany[nameCompany] || [];
itemsByNameCompany[nameCompany].push(input);
}
const outputs = [];
for (let key in itemsByNameCompany) {
const items = itemsByNameCompany[key];
const clients = items.map(item => item.client);
const item = {
...items[0],
client: clients.length === 1 ? clients[0] : clients
};
outputs.push(item);
}
console.log(outputs);
There are lots of way we can do that, Reduce, map, forOf or you can use that as well:
let employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
];
let employees = {};
employee.forEach(employee => {
if(employees[employee.name]){
employees[employee.name].client = [...employees[employee.name].client, employee.client];
} else {
employees[employee.name] = { ...employee, client: [employee.client] };
}
});
console.log(employees); // Simple Object group by name
console.log(Object.values(employees)); // Convert to Array
Hope it will help you to solve.
You will need a function that does this:
Check if the same name and company combination exists
If it exists, then update the client property
If the combination doesn't exist, add it to the same array
const result = [];
function _addData(obj){
const index = result.findIndex((item) =>
item.name === obj.name && item.company === obj.company);
if(index!=-1){
const client = result[index].client;
if(Array.isArray(client)){
client.push(obj.client);
} else{
result[index].client = [client, obj.client];
}
} else{
result.push(obj);
}
}
You need to add a function that checks if the client already is an array. If it is then you can just push the new client. If its a string then you can take the previous value and the new value and write both of them into a new array.
You can do it like this:
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
function addClient(index, client) {
if (Array.isArray(employee[index].client)) {
employee[index].client.push(client);
} else {
employee[index].client = [employee[index].client, client];
}
}
addClient(0, 'New Client')
addClient(0, 'New Client2')
addClient(1, 'New Client3')
console.log(employee)
You can use reduce method to group the employees together it they have equal name and company.
const employees = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const result = employees.reduce((acc, item) => {
const itemInAcc = acc.find(i => i.name === item.name && i.company === item.company);
if (itemInAcc) {
let client = itemInAcc.client
itemInAcc.client = [...(Array.isArray(client) ? client : [client]), item.client];
} else {
acc.push(item)
}
return acc;
}, [])
console.log(result)
Hi you can use reduce to group
employee.reduce((grouped, next) => {
if (grouped.some(gItem => gItem.name === next.name && gItem.company === next.company)) {
grouped = grouped.map(gItem => {
if (gItem.name === next.name && gItem.company === next.company) {
gItem.client = Array.isArray(gItem.client) ? [...gItem.client, next.client] : [gItem.client, next.client]
}
return gItem
})
} else {
grouped.push(next)
}
return grouped
}, [])
welcome to stackoverflow Akshay Kamble
in you case you need to create new JSON object to collect filtered data,
this is my simple code just using forEach
const employee = [{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const newEmployee = [];
employee.forEach(e => {
let obj = newEmployee.find(o => o.name === e.name);
if (obj) {
if (obj.client instanceof Array) {
obj.client.push(e.client)
} else {
obj.client = [e.client]
}
} else {
e.client = [e.client];
newEmployee.push(e);
}
})
console.log(newEmployee)

React merge two states in third state

I have three state, two of them have data from different api and the third state need to merge those two states based on IDs. So, the third state should have every data that state1 have and state2 don't have, and every data that state2 have and state1 don't have.
Api1:
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
}
}
Api2:
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
lastName: 'Second'
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
lastName: 'Second 2'
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
lastName: 'Second 1'
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
lastName: 'Second 3'
}
}
New Array should be (lastName = name + lastName :
data: {
0: {
id: 1234
company: 'String',
name: 'Test'
lastName: 'Second Test'
}
1: {
id: 2345
company: 'String1',
name: 'Test 1'
lastName: 'Second 2 Test 1'
}
2: {
id: 3456
company: 'String2',
name: 'Test 2'
lastName: 'Second 1 Test 2'
}
3: {
id: 4567
company: 'String3',
name: 'Test 3'
lastName: 'Second 3 Test 3'
}
Fetched Data:
const [state1, setState1] = useState([]);
const [state2, setState2] = useState([]);
const [mergeStates, setMergeStates] = useState([]);
useEffects(() => {
fetch("api1")
.then(data =>{
state1(data);
})
fetch("api2")
.then(data =>{
state2(data);
})
}, []);
useEffects(() => {
// Here I want to merge the responses based on IDs
const lastName = companies.map((response) => ({
name: response.name,
lastName: `${response.name} - ${response.lastName}`
}));
setMergeState(lastName);
}, [state1, state2]);
So, the api2 has lastName that api1 doesn't have. So, the mergedStates need to include that.
based on your quetion to merge the two state, you need to iterate over a state either 1 or 2, while merging them.
useEffects(() => {
const keys = Object.keys(state1);
mergedData = keys.map(key => {
// key will be 0,1,2 as with your quetion
return {
...state1[key],
...state2[key],
fullName: state1[key].name + state2[key].lastName
};
});
}, [state1, state2]);

React Get second object if id is equals with another object id

I'm trying to get second object inside data if this id is equals with the id of another object.
With the code below, I'm able to get only ID and not the whole
data: {
0: {
id: 1234,
name: 'Name 1'
},
2: {
id: 4321,
name: 'Name 2'
},
3: {
id: 876,
name: 'Name 3'
}
}
instanceID: 4321
constructor(props) {
super(props);
this.state = {
defaultInstance: 0
}
}
fetch('api)
.then(response => {
console.log('Data fetched', response);
this.setState({
defaultInstance: [response.data.indexOf(response.data.find((instance) => instance.id === response.instanceID))]
});
});
So, what I want to achieve is to add to the defaultInstance that object
2: {
id: 4321,
name: 'Name 2'
},
Try this :
defaultInstance: response.data.filter(d=> d.id == response.instanceID)
This will help you.
let data = {
0: {
id: 1234,
name: 'Name 1'
},
2: {
id: 4321,
name: 'Name 2'
},
3: {
id: 876,
name: 'Name 3'
}
};
let instanceID = 4321;
let result = Object.values(data).filter(chunk => chunk.id === instanceID);
console.log(result);

How to create a new array of objects form object without duplicates ? (ES6)

I would like to create an array of all "department" from the "users" array without duplicate in ES6.
I've tried with forEach, reduce, filter, without success...
Users array:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
]
Result expected:
// Expected
departments = [{
id: 1,
name: 'hello'
},
{
id: 2,
name: 'hello2'
}
] */
My own experiment:
let departments = []
users.forEach(user => {
console.log('-------------------')
console.log(departments)
console.log(user)
console.log(user.department)
console.log(departments.includes(user.department))
if (!departments.includes(user.department)) {
departments.push(user.department)
}
console.log(departments)
})
console.log(departments)
Thanks for your help!
Problem:
Your problem is that you are checking for departments with Array#includes() which is rather used with primitives such as Number and string and doesn't compare objects, try not to use it as it's not compatible with IE also.
Solution:
You can do it using Array#map() and Array#filter() methods:
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
First map the items to keep only the department object.
Then filter the departments to exclude the ones that has the same id.
Demo:
This is a working demo:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
console.log(results);
Just map to the departments, then filter out based on the id:
const ids = new Set;
const result = users
.map(user => user.department)
.filter(({ id }) => !ids.has(id) && ids.add(id));
(This is O(n) as Set lookup / insertion is O(1))
You can use Array.reduce() for that:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
let departments = users.reduce((acc, obj)=>{
let exist = acc.find(({id}) => id === obj.department.id);
if(!exist){
acc.push({id:obj.department.id, name: obj.department.name});
}
return acc;
}, []);
console.log(departments);

Categories