Firstore data is not function - javascript

I am not able to pull data on collection, getting this error
Uncaught TypeError: doc.data is not a function
var db = firebase.firestore();
const docRef = db.collection("Slides");
getRealData = function() {
docRef.onSnapshot(function(doc) {
const myData = doc.data();
console.log(myData);
});
};
getRealData();
I find the solution if I pass fix id then below code work
(Although ID are random generate by firestore)
db.collection("Slides").doc("GYUzWG6jcOPob725wbnF")
.onSnapshot(function(doc) {
console.log("Current data: ", doc.data());
});
Firestore

In your first code sample, you're assuming that the snapshot callback attached to this:
db.collection("Slides")
Is similar to the one attached to this:
db.collection("Slides").doc("GYUzWG6jcOPob725wbnF")
In fact, they are not the same at all.
The first one will query for ALL the documents in the named collection, and it will give you a QuerySnapshot object in the callback. This object does not have a data() method, and you need to iterate it to get all the document snapshots.
The second one will query for only the named document in the named collection, and you will get a DocumentSnapshot object back.
Since you didn't say what you're trying to accomplish, I can't recommend what you should be doing. All I can say is that the two code bits you've shown are not at all alike, and you'd expect them to behave differently.

By the error message looks like data is not a function, it mean that you don't have to use parentheses when assigning it to variable, so change your assignment to this line:
const myData = doc.data;

Actually, my approach was wrong, use get function to get all the data.
docRef.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});

Related

Cant read data from collection in MongoDB Atlas Trigger

New to MongoDB, very new to Atlas. I'm trying to set up a trigger such that it reads all the data from a collection named Config. This is my attempt:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = collection.find().toArray();
console.log(JSON.stringify(config_docs));
}
the function is part of an automatically created realm application called Triggers_RealmApp, which has Cluster0 as a named linked data source. When I go into Collections in Cluster0, TestDB.Config is one of the collections.
Some notes:
it's not throwing an error, but simply returning {}.
When I change context.services.get("Cluster0"); to something else, it throws an error
When I change "TestDB" to a db that doesnt exist, or "Config" to a collection which doesn't exist, I get the same output; {}
I've tried creating new Realm apps, manually creating services, creating new databases and new collections, etc. I keep bumping into the same issue.
The mongo docs reference promises and awaits, which I haven't seen in any examples (link). I tried experimenting with that a bit and got nowhere. From what I can tell, what I've already done is the typical way of doing it.
Images:
Collection:
Linked Data Source:
I ended up taking it up with MongoDB directly, .find() is asynchronous and I was handling it incorrectly. Here is the reply straight from the horses mouth:
As I understand it, you are not getting your expected results from the query you posted above. I know it can be confusing when you are just starting out with a new technology and can't get something to work!
The issue is that the collection.find() function is an asynchronous function. That means it sends out the request but does not wait for the reply before continuing. Instead, it returns a Promise, which is an object that describes the current status of the operation. Since a Promise really isn't an array, your statment collection.find().toArray() is returning an empty object. You write this empty object to the console.log and end your function, probably before the asynchronous call even returns with your data.
There are a couple of ways to deal with this. The first is to make your function an async function and use the await operator to tell your function to wait for the collection.find() function to return before continuing.
exports = async function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = await collection.find().toArray();
console.log(JSON.stringify(config_docs));
};
Notice the async keyword on the first line, and the await keyword on the second to last line.
The second method is to use the .then function to process the results when they return:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
collection.find().toArray().then(config_docs => {
console.log(JSON.stringify(config_docs));
});
};
The connection has to be a connection to the primary replica set and the user log in credentials are of a admin level user (needs to have a permission of cluster admin)

Setting Socket.io room variables

I would like to store some information in the socket room variables, but am getting the following error: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined
This is my code:
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
io.sockets.adapter.rooms[socket.roomId].isOpen = true;
io.sockets.adapter.rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
If I try to log socket.roomId it would return something like rBAhx0. And when I log io.sockets.adapter.rooms, I get the following:
Map {
'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}
However, when I try to log io.sockets.adapter.rooms[socket.roomId], it returns undefined. How can I fix this?
Socket.io made some breaking changes in the newest version and rooms cannot be accessed like they used to. It changed a lot of objects and arrays into Maps and Sets, you can see it in the logs you've posted.
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
Accessing properties of a Map works differently than accessing properties of a normal Object. Example:
const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar
Same applies to Sets, however in your case querying this Set in particular is probably a wrong approach, as this set only holds a collection of ids, and not actual room objects. Even if you were to get a value out of the Set, you could not access it's properties (host, isOpen and players) since it is only a string.
The version 3.0 made accessing the list of all rooms directly impossible I'm afraid. However the adapter now has a property socketRooms, which can be used in place of it.
In order to access rooms of a socket easier, you should access them like so:
io.sockets.adapter.socketRooms(socketId);
However that would still just return a list of strings.
The simplest solution to this problem would be to create an external variable outside of the connection scope.
const rooms = {};
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
rooms[socket.roomId].host = socket.username;
rooms[socket.roomId].isOpen = true;
rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
As you can see in your own log, io.sockets.adapter.rooms is a Map object (in the later versions of socket.io), not a plain object. That means that a room is accessed with .get(), not with plain indexing such as what you are using:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
That simply won't work because there is no plain socket.roomId property on the Map.
If you wanted to get the room object, you would have to do this:
let roomObj = io.sockets.adapter.rooms.get(socket.roomId);
roomObj.host = socket.username;
In my opinion, I wouldn't mess with the room objects directly. We already know that socket.io has changed their design before, rendering any code that tried to use them as broken and in need of redesign. Instead, I'd probably just create my own data structure where I stored my own data and then I won't be dependent upon the whims of how socket.io changes their design. Accessing the room objects directly is not a committed design or committed API of socket.io. They can change how it works whenever they want and have, indeed, changed it recently.
After the breaking update from Socket.io of changing Array to Map.
A simple solution would be changing:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
to:
io.sockets.adapter.get(socket.roomId).host = socket.username;

Firestore References

i want to add a reference field in a document into Firestore using nodejs but i can't do it.
I wrote this code:
async function pushType(IDType) {
const size = await getID();
const IDClient = (size).toString();
const docRef = firebase.firestore().collection('Clients').doc(IDClient);
await docRef.set({
ID_Type: firebase.firestore().doc('Types/'+IDType).ref,
});
}
async function getID(){
const snapshot = await firebase.firestore().collection('Clients').get();
return snapshot.size;
}
The error is: "Function Document.Reference.set() called with invalid data. Unsupported field value: undefined (found in field ID_Type in document Clients/7)" where 7 is the ID of the document where i want to add the field ID_Type.
Can anyone help me to understand what i'm wrong and how can i fix it? Thank you
Your code firebase.firestore().doc('Types/'+IDType) returns a DocumentReference. As you can see from the API docs, there is no property called ref on that, so it will be undefined. In fact, that DocumentReference is exactly what you want to provide to Firestore here. So just remove ref:
await docRef.set({
ID_Type: firebase.firestore().doc('Types/'+IDType)
});
When you provide a DocumentReference object to a Firestore document like this, it will create a reference type field.

How to modify array data with foreach

I took data from MongoDB and executed it with forEach statement.
However, I want to change the date data from 2018-08-01T00:00:00.000Z' to 'YYYY-MM.
When I ran console.log using the Moment package of Nodejs, the data I wanted was printed out.
console.log(moment(repo.created_at).format("YYYY-MM")) code normally outputs the data I want, but the original data did not change when saved as repo.created.
I don't know how to solve this problem.
/* GET MyPage Page */
router.get(`/:userId/admin/mypage`, function (req, res, next) {
let userId = req.params.userId; // UserID Variable
Repo.find({ // MongoDB
'owner.login': userId
}, function (err, repo) {
if (err) throw err;
// console.log(repo);
repo.forEach(function (repo) {
repo.created_at = moment(repo.created_at).format("YYYY-MM");
console.log(repo.created_at)
repo.updated_at = moment(repo.updated_at).format("YYYY-MM");
console.log(repo.updated_at)
})
res.render('mypage/main', {
userId: userId,
dataArray: repo,
})
})
When I console.log it it's still like this.
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2017-08-01T00:00:00.000Z
But with console.log(moment(repo.created_at).format("YYYY-MM")) code. it works fine
2018-09
2018-09
2018-09
2018-09
2018-09
2018-09
2018-08
I recommend to use a map function instead of forEach, because it is normally more performant and doesn't mutate the original array. (A short demonstration about the differences between map and forEach is given in the article "JavaScript — Map vs. ForEach
What’s the difference between Map and forEach in JavaScript?" at codeburst.io.)
Furthermore you should use a different name for your parameter names, because it can cause confusion about what is meant. This is called "scope" in JavaScript and it is also an example of "variable shadowing" or "name shadowing" and can cause subtle bugs. Therefor, you should use this with caution. In your case, you could name the loop parameter holding the array entries of your repo something like "repoElement", "repoItem", "repoEntry" or you find a more descriptive name.
You also say
but the original data did not change when saved as repo.created
If you do something like repoItem.created = moment(repoItem.created_at).format("YYYY-MM");, you add a new property to the repoItem-object, but the created_at property remains untouched. In the following example, I have named the formatted dates createdAt and updatedAt, so that these new properties get created. But every new repoItem will also have the untouched created_at and updated_at properties available next to the new properties createdAt and updatedAt.
// ...
const dataArray = repo.map(repoItem => ({
...repoItem,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
// ...
If you don't want to include the originally formatted data, you have to make another step and destruct the data that you want before adding it to the new object. You can also destruct everything and then delete created_at and updated_at. This can be done like this:
const newRepoItem = { ...repoItem };
delete newRepoItem.created_at;
delete newRepoItem.updated_at;
or by using the "rest" parameter like this:
const { created_at, updated_at, ...newRepoItem } = repoItem;
// now, you can use the new variable newRepoItem.
More information about removing unwanted properties from an object can be found here: Remove value from object without mutation.
I would use the version that is better understood by you and your team. In my opinion, the keyword delete is the solution that is easier to understand for most people, because it literally says what it does. But if you like destructuring and are used to it, this can be the more compact and readable solution. Don't forget, that it could also be possible, that you only have a few properties in your repoItem that you care about. In this case, you can simply destructure the items that you want to keep. This could look like in the following example:
// ...
const dataArray = repo.map(repoItem => {
const { firstPropertyToKeep, secondPropertyToKeep } = repoItem;
return ({
firstPropertyToKeep,
secondPropertyToKeep,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
I have create a REPL where you can see the difference between forEach and map: https://repl.it/#helgedrews/forEach-vs-map
I hope this helps a bit.
Just use .map()
Example:
const updatedRepos = repos.map((repo)=>{
return {
...repo,
created_at: moment(repo.created_at).format("YYYY-MM");
updated_at: moment(repo.updated_at).format("YYYY-MM");
}
})
use [].map instead or set variable to save the result

In Typescript, how do I maintain an objects type when storing and then accessing said object from an array?

In typescript, I have noticed that when I take a complex object and put it in an array, when I attempt to access that object from the array it loses its type and instead simply becomes of type object.
For example
let myArray = return await this.browser.evaluate((sel: string) => Array.from(document.querySelectorAll(sel)), selector)
document.querySelectorAll(sel) returns a NodeList<Element> which is ArrayLike. Array.from should convert the NodeList into an array of elements, but once the array is formed all of the array elements lose their Element type
I have a function that will only accept parameters of type Element, but when I try to pass in myArray[0] as a parameter to said function, I get the error: Error: Unsupported target type: object
I have tried so many different things to try and get the array to maintain its object type that it would be difficult to explain each and every one of them. I am wondering how can I create an array of Elements and have them continue to be Elements when accessed later instead of generic objects
Here is a little more context in the testing I've done
I am going to this page: https://www.w3schools.com/html/html_tables.asp
and the selector I am passing into evaluate is table[id="customers"] tbody tr This should match with the 6 rows that appear in the table.
let test = await this.browser.evaluate((sel: string) =>
Array.from(document.querySelectorAll(sel)), selector)
console.log('testy1: ', test)
console.log('testy2: ', test[0])
console.log('testy3: ', typeof(test[0]))
When I run the above code this is the output I get in the console log:
testy1: [ {}, {}, {}, {}, {}, {}, {} ]
testy2: {}
testy3: object
It seems to be matching grabbing the elements from the page because it is correctly returning 6 elements. but maybe the issue is that the objects returned are empty? I am not sure.
I think my problem may be related to this question: puppeteer page.evaluate querySelectorAll return empty objects
but the solution to that question doesn't work for me because href isn't a property of object type Element
The problem here is that the function you are passing to page.evaluate is run inside the browser context (inside the browser page). To send the results from the browser context to the Node.js environment, the results are serialized.
See the return type in the docs for page.evaluate:
returns: Promise<Serializable> Promise which resolves to the return value of pageFunction
The Serializable here means that your data will be passed to the Node.js environment via JSON.stringify and there automatically parsed for you. This process will however remove any non-serializable properties of objects. This is the reason why you end up with many empty objects.
Get element handles in puppeteer
To get an handle of an element on the page, you need to use the page.$, which creates an object (in your Node.js environment) that is linked to an element inside the browser context. These kind of handles can also be passed to page.evaluate calls. To query for multiple element, you can use the function page.$$.
Code sample
Here is an example, which first queries an element and then passes the element handle to an evaluate function to read an attribute.
const elementHandle = await page.$('a');
const result = await page.evaluate(el => el.href, elementHandle);
Usage of TypeScript
The problem regarding TypeScript is, that TypeScript is not able to predict the type correctly in this scenario. For the TypeScript compiler this looks like a normal function call while in reality, the function is send to the client to be executed. Therefore, you have to cast the type yourself in this case as otherwise Typescript will just assume any as argument type:
const elementHandle = await page.$('a');
const result = await page.evaluate((el: { href: string }) => el.href, elementHandle);
i'm facing the same problem with types definition, and Element is not exported from any Lib.
To get apropriate intelisense with el methods, i changed my code with below example:
From:
...
await page.waitForTimeout(3000)
await page.waitForSelector('button[type=submit]')
await page.$eval('button[type=submit]', el => el.click())
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...
To this:
...
await page.waitForTimeout(3000)
await page.waitForSelector('button[type=submit]')
await page.$('button[type=submit]').then(el => {
el.click()
})
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...
puppeteer element intelisense example
PS: I found the issue in definition types on DefinedType Github https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419

Categories

Resources