TypeError: Cannot read property 'map' of undefined but state has been stored - javascript

I am trying to get a specific team data from the database and store it in a state. But when I map an array inside that data it returns an error. When I console log my state it returns the data below
createdAt: "2021-03-19T13:36:22.868Z"
gameEvent: "basketball"
players: Array(5)
0: {_id: "605ea59c5cdf492b48987107", name: "Jerry Ale", jerseyNumber: "12"}
1: {_id: "605ea59c5cdf492b48987108", name: "Judel Agur", jerseyNumber: "14"}
2: {_id: "605ea59c5cdf492b48987109", name: "qwe", jerseyNumber: "12"}
3: {_id: "605ea59c5cdf492b4898710a", name: "qwe", jerseyNumber: "12"}
4: {_id: "605ea59c5cdf492b4898710b", name: "qwe", jerseyNumber: "12"}
length: 5
__proto__: Array(0)
teamName: "Balilihan"
updatedAt: "2021-03-27T03:25:16.148Z"
__v: 0
_id: "6054a8d63fec5c24389624ac"
I have an useEffect to gather this;
useEffect(() => {
const getTeam = async () => {
try {
const { data } = await fetchContext.authAxios.get('get-all-teams');
setIsLoaded(true);
if (isLoaded === true) {
setCurrentTeam(data.find((team) => team._id === row._id));
}
} catch (err) {
console.log(err);
}
};
getTeam();
}, [fetchContext, row, isLoaded]);
and I map the players array in a new variable because I want a controlled inputs for my form because I am updating the data. I am using Formik by the way
let playersOfTeam = currentTeam.players.map((player, index) => [
{
name: player.name,
jerseyNumber: player.jerseyNumber,
},
]);
But when I just get a specific value like the teamName it returns the teamName and when I console log currentTeam.players it returns what I expected to get. I am confused why I get this kind of error

Your data is undefined when the component is first mounted. This is because useEffect runs after render.
So adding a null check is the solution. Personally I prefer optional chaining. Simply change to:
let playersOfTeam = currentTeam?.players?.map((player, index) => [
{
name: player.name,
jerseyNumber: player.jerseyNumber,
},
]);

Related

Javascript extracting values from deeply nested array object structure

I'm trying to pull out specific fields from backend data to prep the body of a table. The data coming in has the structure of:
[
{
_id: "63056cee252b83f4bc8f97e9",
goals: [
{ title: "Cook" },
{ title: "Budget" }
],
visitEnd: "2022-08-18T00:30:00.000Z",
visitStart: "2022-08-17T21:30:00.000Z",
},
{
_id: "63223586798c6b2658a0d576",
goals: [
{ title: "Cook" },
{ title: "Budget" },
{ title: "Clean" }
],
visitEnd: "2022-09-13T00:30:00.000Z",
visitStart: "2022-09-12T22:00:00.000Z"
},
{
_id: "63542ecfca5bd097a0d9acaf",
goals: [
{ title: "Cook" },
{ title: "Clean" }
],
visitEnd: "2022-10-12T19:00:11.000Z",
visitStart: "2022-10-12T17:00:00.000Z",
}]
Since the table headers are by month/year, I'm using lodash to group them by month, which gets me here:
Object { 7: (2) […], 8: (2) […], 9: (2) […] }
​
7: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Firm Office in LA", visitStart: "2022-08-17T21:30:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-08-11T21:00:57.000Z", … }
​​
length: 2
​​
<prototype>: Array []
​
8: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-09-12T22:00:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-09-21T21:00:00.000Z", … }
​​
length: 2
​​
<prototype>: Array []
​
9: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-10-12T17:00:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-10-21T21:00:00.000Z", … }
​​
length: 2
But now I'm stuck since I want to isolate the fields of the goals array, which is within the objects, within the array of each month, which is contained in an object. I've tried playing around with Object.keys and maps, and then from here: https://dev.to/flexdinesh/accessing-nested-objects-in-javascript--9m4 came across a function to get deeply nested items. But I'm still messing this up, and my head is spinning trying to make sense of it. I looked at lodash's map and property, but was not sure how to implement given the layers of nesting I'm trying to work through on dynamically named arrays within the groupBy object. Heres where I'm at, but I'm getting the error i.map is not a function
const sort = groupBy(visits, ({visitEnd})=> new Date(visitEnd).getMonth());
console.log("sort 1: ", sort)
const stage = Object.keys(sort).map((i) => {
{ i.map((el) => getNestedObject(el, ['goals', 'title'])) }
})
console.log("sort 2: ", stage)
My javascript knowledge is terrible which doesn't help...
The error you're getting, i.map is not a function, means that the variable i is not an array. Based on the data you supplied in your post i is an object.
Iterate the result of the sorted month/year data using Object.entries() versus Object.keys().
To get a list of unique goals per month with output that looks like:
{
7: ["Cook", "Spend", "Clean"],
8: ["Cook", "Budget", "Clean"],
9: ["Cook", "Budget", "Scrub", "Fold", "Rest", "Wash"]
}
const dataSortedByMoYrObj = {
7: [
{
user: "62410a1dcaac9a3d0528de7a", location: "Firm Office in LA", visitStart: "2022-08-17T21:30:00.000Z",
goals: [
{ title: "Cook" },
{ title: "Spend" },
{ title: "Clean" }
]
},
{
user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-08-11T21:00:57.000Z",
goals: [
{ title: "Cook" },
{ title: "Clean" }
]
}
],
8: [
{
user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-09-12T22:00:00.000Z",
goals: [
{ title: "Cook" },
{ title: "Budget" },
{ title: "Clean" }
]
},
{ user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-09-21T21:00:00.000Z" }
],
9: [
{
user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-10-12T17:00:00.000Z",
goals: [
{ title: "Cook" },
{ title: "Budget" },
{ title: "Scrub" }
]
},
{
user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-10-21T21:00:00.000Z",
goals: [
{ title: "Fold" },
{ title: "Rest" },
{ title: "Wash" }
]
}
]
};
// 'const getNestedObject' code sourced from:
// https://dev.to/flexdinesh/accessing-nested-objects-in-javascript--9m4
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
const goalsByMonthYearObj = {};
Object.entries(dataSortedByMoYrObj).forEach(([month, users]) => {
// 'month' represents the key.
// 'users' is an array of objects listed for each month.
let goalsByMonth = [];
users.map(user => {
const goalsProp = getNestedObject(user, ['goals']);
// Check if the 'goals' property is a valid.
// If 'goals' property is 'null' or 'undefined',
// '!Array.isArray(null)' returns 'true'.
if (!Array.isArray(goalsProp)) { return; }
// Convert list of goal objects (e.g. '{title: Budget}')
// to an array using 'goalsProp.map()' and then
// concatenate goals array to the existing
// goals-by-month array.
goalsByMonth = goalsByMonth.concat(goalsProp.map(goal => goal.title));
});
// Add array of unique goals for each month
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
goalsByMonthYearObj[month] = [...new Set(goalsByMonth)];
});
console.log(goalsByMonthYearObj);
(Original code that's not as concise as above snippet.)
const goalsByMonthYearObj = {};
// Reference to 'Object.entries()' at:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
for (const [key, value] of Object.entries(dataSortedByMoYrObj)) {
// 'key' represents a month index.
// 'value' contains an array of objects listed for each month index.
//console.log(`${key}: ${value}`);
const goalsByMonth = [];
value.forEach(item => {
// The 'goals' property is only one level deep so
// it's not necessary to use the 'getNestedObject()'
// function.
// For example: const goalsProp = item.goals;
// The function is useful for more deeply
// embedded properties.
const goalsProp = getNestedObject(item, ['goals']);
if (!Array.isArray(goalsProp)) { return; }
goalsProp.forEach(goal => {
if (!goal.title) { return; }
goalsByMonth.push(goal.title);
});
});
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
const uniqueGoals = [...new Set(goalsByMonth)];
goalsByMonthYearObj[key] = uniqueGoals;
}
console.log(goalsByMonthYearObj);

Sequelize: Special methods don't update the database? - Problem updating instance

I've spent a decent chunk of the day confused about the behaviour of one of my methods that should create an instance of a Model, Company, that has a Contact.
I feel like it should be simple, but it's just not outputting the contact field as I'd expect, even though it is saving it to the database correctly:
await sequelize.transaction(async (t) => {
const company = await Company.create({ name: req.body.companyName }, { transaction: t });
const contact = await Contact.create({position: req.body.position}, { transaction: t });
await company.addContact(contact, {transaction: t}); // This method does exist
await company.save(); // Thought this might help in case `addContact` didn't update the db
console.log(company.dataValues); // {id:5, name: 'test'}
console.log(contact.dataValues); // {id: 7, position: 'Legend'}
const test = await Company.findOne({where: { name: req.body.companyName }, transaction: t});
console.log(test.dataValues); // {id: 5, name: 'test'}, no contact property
console.log(company.contacts); // is this not posssible instead?
//...
I was expecting this as the output:
{
id: 5,
name: 'test',
contacts: [{ id: 7, position: 'legend' }]
}
Really can't understand why it's not working. Any help would be really appreciated at this point!
** Associations:
Company.hasMany(Contact);
Contact.belongsTo(Company);

JS Firestore 'load more' pagination ignoring startAfter() and loading old data

I'm implementing a pagination where the user clicks a button to load more items.
I'm following the docs at https://firebase.google.com/docs/firestore/query-data/query-cursors (paginate a query)
I'm passing in the last item id to the startAfter() function. This should make the next query return items that comes after the passed in item id.
What actually is happening, is that the previous items get loaded again.
So if the initial fetches looks like:
[
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
2: {id: "3", stuff: ...}
]
the id 3 will get passed into startAfter(), and should load
[
0: {id: "4", stuff: ...}
1: {id: "5", stuff: ...}
]
instead, it loads the initial items
[
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
]
So I end up displaying duplicate items when I press my 'load more' button.
initial fetch function:
const [recipes, setRecipes] = useState([]);
useEffect(() => {
const fetchData = async () => {
const obj = [];
const firstFetch = await db
.collection('recipes')
.orderBy('createdAt', 'desc')
.limit(3)
.get();
const snapshot = await firstFetch;
snapshot.forEach((doc) => {
obj.push({ id: doc.id, ...doc.data() });
});
const allRecipes = {
userRecipes: obj,
};
setRecipes(allRecipes.userRecipes);
};
fetchData();
}, []);
The recipe variable logs:
[
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
2: {id: "3", stuff: ...}
]
The button to load more items:
<button
type="button"
onClick={() => showMore({ recipe: recipes[recipes.length - 1] })}
>
Show More
</button>
Which calls the showMore function:
const showMore = async ({ recipe }) => {
const data = await db
.collection('recipes')
.orderBy('createdAt', 'desc')
.startAfter(recipe)
.limit(2)
.get();
const moreData = data.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
setRecipes([...recipes, ...moreData]);
};
If I log the 'moreData' variable:
[
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
]
if I log the updated 'recipe' variable:
[
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
2: {id: "3", stuff: ...}
0: {id: "1", stuff: ...}
1: {id: "2", stuff: ...}
]
So to me, it seems like it's ignoring the startAfter() function, and loading everything from the start instead.
Any pointers on what am I doing wrong?
Edit:
The actual data in the db looks like this:
{
about: "Non magnam quasi tempore."
createdAt: t {seconds: 1596741068, nanoseconds: 531000000}
description: "Ea quibusdam odit quasi corporis et nostrum."
imageUrl: "http://lorempixel.com/640/480/food"
ingredients: [{…}]
likes: []
title: "Ella"
userId: "U8n09jb0ZENwrZhDeYrJUSikcM12"
userSaves: ["uM7A4r5O6ENiX3qTL4tGhDXYA3Z2"]
username: "foobar"
}
the ID is the name of the document. The ID gets appended to the document in the initial fetch function.
SOLUTION:
Looking at Frank van Puffelen's answer, I had to provide the complete snapshot (which includes all info about the item), not only the array with items in it.
By creating a new hook with useState, I could store the state of the last snapshot in it:
const last = snapshot.docs[snapshot.docs.length - 1]
setLastDocSnap(last)
in my fetchMore() function, I was able to se the state in the startAfter() function:
const data = await db
.collection('recipes')
.orderBy('createdAt')
.limit(limit)
.startAfter(lastDocSnap)
.get();
I am not able to reproduce the problem. I've simplified your code to:
ref.orderBy('id')
.limit(3)
.get()
.then(function(querySnapshot) {
console.log("Initial query: "+querySnapshot.docs.length+" results");
querySnapshot.forEach(function(doc) {
console.log(doc.data().id);
})
var last = querySnapshot.docs[querySnapshot.docs.length - 1];
ref.orderBy('id')
.startAfter(last)
.limit(3)
.get()
.then(function(querySnapshot) {
console.log("Second query: "+querySnapshot.docs.length+" results");
querySnapshot.forEach(function(doc) {
console.log(doc.data().id);
})
});
});
And then run it on a collection with 5 documents, with id values 1 through 5. It's output is:
Initial query: 3 results
1
2
3
Second query: 2 results
4
5
Which is as I'd expect it to be.
You can see the running code here: https://jsbin.com/qusijaw/edit?js,console
If my results don't help you get it working on your end, can you try to reproduce the problem in an environment like jsbin, so that we can take a look at it?

mapping items and flattening into single array in reactjs

Currently I have a reactjs function that simply queries a pouchDB document, gets 7 records and then I'm trying to flatten those records in order to store in state. The problem is that, right now when I console.log docCalories I get this:
(7) [{…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {caloriesBurned: "5345", createdAt: "2020-03-28T05:15:24.369Z"}
1: {caloriesBurned: "1234", createdAt: "2020-03-28T10:39:16.901Z"}
2: {caloriesBurned: "1122", createdAt: "2020-03-28T10:32:03.100Z"}
3: {caloriesBurned: "1234", createdAt: "2020-03-28T05:16:54.846Z"}
4: {caloriesBurned: "1234", createdAt: "2020-03-28T10:21:31.092Z"}
5: {caloriesBurned: "1234", createdAt: "2020-03-28T05:08:00.791Z"}
6: {caloriesBurned: "1234", createdAt: "2020-03-28T05:07:35.940Z"}
length: 7__proto__: Array(0)
but I want to get something that looks like this:
map: [5345,1234,1122,1234,1234,1234,1234]
So basically one object that contains the 7 numbers from each doc's caloriesBurned value
What am I doing wrong here and how can I properly put these into one array/object?
setMax = () => {
this.state.caloriesDB.db.find({
selector: {
$and: [
{_id: {"$gte": null}},
{caloriesBurned: {$exists: true}},
{createdAt: {$exists: true}}
]
},
fields: ['caloriesBurned', 'createdAt'],
sort: [{'_id':'desc'}],
limit: 7
}).then(result => {
const newDocs = result.docs;
const docCalories = newDocs.map((caloriesBurned) => caloriesBurned)
console.log('this is map');
console.log(docCalories);
}).catch((err) =>{
console.log(err);
});
}
You're returning the entire object in your map function, instead you should only send the caloriesBurned property.
const docCalories = newDocs.map((data) => data.caloriesBurned)
or if you like, we can destructrure data and have
const docCalories = newDocs.map(({caloriesBurned}) => caloriesBurned)
What Dupocas has written in the comments is correct.
newDocs is a list of objects and with this code:
const docCalories = newDocs.map((caloriesBurned) => caloriesBurned)
you will just get another list that is just like newDocs. What you want to return from the map function is a specific key, so try:
const docCalories = newDocs.map(doc => doc.caloriesBurned)
considering docCalories value in m2 by creating map, you can do something like this -
const m2 = new Map(Object.entries([{
0: {
caloriesBurned: "5345",
createdAt: "2020-03-28T05:15:24.369Z"
}
},
{
1: {
caloriesBurned: "1234",
createdAt: "2020-03-28T10:39:16.901Z"
}
},
{
2: {
caloriesBurned: "1122",
createdAt: "2020-03-28T10:32:03.100Z"
}
},
{
3: {
caloriesBurned: "1234",
createdAt: "2020-03-28T05:16:54.846Z"
}
},
{
4: {
caloriesBurned: "1234",
createdAt: "2020-03-28T10:21:31.092Z"
}
},
{
5: {
caloriesBurned: "1234",
createdAt: "2020-03-28T05:08:00.791Z"
}
},
{
6: {
caloriesBurned: "1234",
createdAt: "2020-03-28T05:07:35.940Z"
}
}
]))
var store = [];
Array.from(m2).map(([key, value]) => store.push(value[key].caloriesBurned));
console.log(store);

Combining two objects into one object and sorting by timestamp

In Laravel, I have used this approach to combine to collections together and returning as one collection.
$collection = $messages->merge($texts)->sortByDesc('created_at')
If I dd($colection), it shows Collection object all combined and sorted together.
Then I tried to send it to vue via ajax, however, the data is separated again. So my object looks like this:
item: {
messages: [
0: { ... }
1: { ... }
2: { ... }
3: { ... }
],
texts: [
0: { ... }
1: { ... }
]
}
this is because return response()->json('item' => '$collection') separates them as messages and texts again.
I tried combining them like this, but it overwritten the values (I assume because ids are same).
vm item = this;
// in response of ajax get,
.then(function(response) {
var item = response.data.item;
Object.assign(vm.item.messages, vm.item.texts);
});
What is the right way to combine texts into messages and sorting them by timestamps? They all have created_at in the first level of objects like this:
messages: [
0: { created_at: ... }
],
texts: [
0: { created_at: ... }
]
Update: After icepickle's answer, with concat, I was able to combine them in messages array. Now, I have an issue for created_at values as they are converted to strings. Here are some test data. This is what I got after ordering:
messages: [
0: {
msg: 'hello',
created_at: "2017-10-12 00:48:59"
},
1: {
msg: 'mellow',
created_at: "2017-10-11 16:05:01"
},
2: {
msg: 'meow',
created_at: "2017-10-11 15:07:06"
},
4: {
msg: 'test'
created_at: "2017-10-11 17:13:24"
}
5: {
msg: 'latest'
created_at: "2017-10-12 00:49:17"
}
],
Wouldn't it be enough to concat the arrays, and then sort?
A bit like
let result = ([]).concat(item.messages, item.texts);
or in es6
let result = [...item.messages, ...item.texts]
and then calling sort on the result
// in place sort, result will be sorted, and return the sorted array
result.sort((a, b) => a.created_at - b.created_at);
const items = {
messages: [
{
msg: 'hello',
created_at: "2017-10-12 00:48:59"
},
{
msg: 'mellow',
created_at: "2017-10-11 16:05:01"
},
{
msg: 'meow',
created_at: "2017-10-11 15:07:06"
}
],
texts: [
{
msg: 'test',
created_at: "2017-10-11 17:13:24"
},
{
msg: 'latest',
created_at: "2017-10-12 00:49:17"
}
]
};
let result = [...items.messages, ...items.texts].sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
console.log( result );

Categories

Resources