Why is snap.data().id not defined? - javascript

I can't seem to get the id of the document I'm trying to retrieve.
I have looked at a lot of examples on the web and they all seem to be doing exactly what i'm doing.
exports.moveToProfile = functions.firestore
.document("tempProfiles/{id}")
.onCreate(async (snap, context) => {
const id = snap.data().id;
const displayName = snap.data().displayName;
const profile = await db
.collection("profiles")
.doc(id)
.set({
displayName: displayName,
points: 0
});
return profile;
});

In your code, data is a DocumentSnapshot type object. As you can see from the linked API documentation, the ID of the document represented by that object is its id property. data() gives you all of its fields (and the formal doucment ID is not one of them, unless you wrote it as a field.) So, you can get the ID with data.id.

If you want to use wildcards and parameters then you can use context.params.
See https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters .
// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
.document('users/{userId}')
.onWrite((change, context) => {
// If we set `/users/marie` to {name: "Marie"} then
// context.params.userId == "marie"
// ... and ...
// change.after.denter code hereata() == {name: "Marie"}
});

Related

how to use variable in firestore query

I use firebase firestore db. This project have two main collection "securecollection" and "publiccollection". The securecollection is where all important data is stored and only accessible to authenticated users. But in order for some information to be visible to guest users, I am copying them into the publiccollection. Then, I want to save the id of this newly created public document in the secure document.
When I write a variable
db.collection('securecollection').doc(secureDocid).update({ inside the query sentence, I get this error:
Uncaught (in promise) TypeError: o.indexOf is not a function
If I write the id as text db.collection('securecollection').doc('7llDqIWlmE5VM69Zzle2').update({, the code works.
Here is my code:
function toggleOlustur(secureDocid) {
const db = firebase.firestore();
db.collection("securecollection").get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
if (doc.id == secureDocid) {
db.collection("publiccollection").add({
oad: doc.data().oad,
osad: doc.data().osad,
opuan: doc.data().opuan,
odtarih: doc.data().odtarih,
odurum: "1",
})
.then((docRef) => {
db.collection('securecollection').doc(secureDocid).update({
preference: docRef.id
})
});
}
});
});
}
The doc() method expects a string argument, and from the error message it sounds like your secureDocid is not a string value. If you console.log(JSON.stringify(secureDocid)) you can see what it outputs, which might help to figure out the problem.

How do you avoid missing data when using limit, orderBy, and startAfter with Firebase?

Isn't it true that you could miss data if what you are ordering by does not have a unique value?
For example if I have a function that says:
export async function getCities(){
const result = await firebase
.firestore()
.collection('cities')
.orderBy('population', 'desc')
.limit(5)
.get();
const CityResults = result.docs.map((city)=> ({
...city.data(),
docId: city.id
}));
if I limit to 5 and my last piece of data has a population of 1000, my getMoreCities() funciton would say
startAfter(1000)
But say there were 7 cities with a population of exactly 1000, you would miss the data on both of your firestore calls.
It seems silly you cant startAfter(documentId)
is there a workaround?
Firestore implicitly adds the document ID to each query where you specify an anchor document, so if you pass in a DocumentSnapshot it will already work.
You should also be able to pass the document ID as the last argument to your startAfter variant, although I must admit I always use the DocumentSnapshot for this purpose myself.
Here's an example of what this would look like:
citiesRef.orderBy("population").startAt(860000).limit(1).get().then((snapshot) => {
var lastdoc;
snapshot.forEach(function(doc) {
console.log("1: "+doc.id);
lastdoc = doc;
});
citiesRef.orderBy("population").startAfter(lastdoc).limit(1).get().then((snapshot) => {
snapshot.forEach(function(doc) {
console.log("2: "+doc.id);
lastdoc = doc;
});
});
});
Working version: https://jsbin.com/sibimux/edit?js,console

Firebase Realtime Database - Set up multiple .on() listeners based on access nodes

I am utilising a Firebase Realtime Database with the below structure.
I wish to fetch all "notes" a user has access to and subscribe to changes in those notes.
notes: {
"noteId-1345" : {
"access" : {
"author": "1234567890"
"members": {
"1234567890": 0 <--- Author
"0987654321": 1 <--- Member
}
},
"data" : {
"title": "Hello",
"content": "Konichiwa!",
"comment": "123"
}
}
}
( I am aware this structure could, ideally, be more flat. :) )
To fetch all notes a user has access to - I keep an additional user_notes node in the root:
Whenever I associate a user (update of members) with a note, I update both /notes/$noteid and /user_notes/$uid.
user_notes: {
"$uid": {
"noteId-1345": {
myHide: false,
mySortOrder: 0,
title: "Hello"
}
}
}
When fetching data I wish to set up subscription to all notes the user has access to.
I begin by fetching the ids for notes the user has access to and then attach listeners to subscribe to updates in each note.
const uid = getState().auth.uid
let collectedNotes = {}
...
database.ref(`user_notes/${uid}`).on('value', (myAccessSnaps) => {
myAccessSnaps.forEach((accessSnap) => {
const noteId = accessSnap.key
const privateData = {'personalData': {...accessSnap.val()}}
database.ref(`notes/${noteId}`).on('value', (noteSnap)=>{
const notData = noteSnap.val()
const fullData = { ...privateData, ...notData }
const note = {
id: noteSnap.key,
...fullData
}
collectedNotes[note.id] = note
...
})
}))
})
(Of course, I will need to use .off() to detach listeners before setting up new ones)
This is somewhat problematic since I have to attach one listener per note - and there could be hundreds of notes in the database.
Is this the most efficient approach? - It seems inefficient.
Is there a way to listen to ALL the notes a user has acess to in the /notes path with one listener? Or is my approaching altogether wrong? :)
Kind regards /K
After understanding that .on() does not return a promise - I became much easier to solve my problem.
Attaching a lot of .on() listeners did not make any sense to me.
The easiest approach for me was to:
1 - Update my access nodes with a time stamp updatedAt each time a note was updated
2 - Load initial data using .once() that returns promise,see code below.
3 - Set up separate subscription for when access nodes changes
let myPromises = []
database.ref(`user_notes/${uid}`).once('value', (myAccessSnaps) => {
myAccessSnaps.forEach((accessSnap) => {
const noteId = accessSnap.key
const privateData = {'personalData': {...accessSnap.val()}}
myPromises.push(
database.ref(`notes/${noteId}`).once('value', (noteSnap)=>{
const notData = noteSnap.val()
const fullData = { ...privateData, ...notData }
const note = {
id: noteSnap.key,
...fullData
}
collectedNotes[note.id] = note
...
})
)
}))
})
return Promise.all(myPromises)
.then(() => {
dispatch(setNotes(categories))
...
// Set up subscription after initial load
database.ref(`user_notes/${uid}`).on('value', (myAccessSnaps) => {
...
// Use access node listener only - gets updated by 'updatedAt'
database.ref(`notes/${noteId}`).once('value', (noteSnap)=>{
//Collect and dispatch data
Kind regards /K

How to fetch and populate data from firebase collections in react correctly?

So i have component called Agents, that's by it's nature is table with rows that contain data about agents.
I want to get full data about agents from firestore, populate it with data from another collection in firestore, and then render it to the screen.
Here is working piece of code, but i have some questions:
Why now i have render with some delay? (First i have spinner about loading - it's okay, i expect it, but then spinner hides and i see 1 agent in table for 0.5s, and then i see all data. I want to see all data after spinner gone. What is the problem of my code?
How to populate data. Maybe there is another way to do that, or maybe there is another good practice, cause i think my "componentDidMount" is horrible, the solutions that i use are not common i guess.
So for 2 question i will describe what i want:
In users collection i have documents, they are objects, that have property called - profile. That property contains ID of document from collection called roles! I want to get that data by that document id. That's why my componendDidMount is async, cause i want to wait till i get data from roles collection by id of document.
Help me please optimise my code )
class Agents extends Component {
state = {
isLoading: true,
agents: [],
showOptionsFor: null
}
async componentDidMount() {
firebase
.firestore()
.collection('users')
.orderBy('lastName')
.onSnapshot(async snapshot => {
let changes = snapshot.docChanges()
let agents = this.state.agents
for (const change of changes) {
const agent = {
id: change.doc.id,
...change.doc.data()
}
if (change.type === 'added' || change.type === 'modified') {
await firebase
.firestore()
.collection('roles')
.doc(change.doc.data().profile).get().then( response => {
agent['role'] = response.data().profile
agents.push(agent)
}
)
}
if (change.type === 'modified') {
const newAgentsArray = agents.filter(element => {
return element.id !== change.doc.id
})
agents = newAgentsArray
agents.push(agent)
}
}
this.setState({
agents: agents,
isLoading: false
})
})
}

Use existing variables when using refetchQueries in react-apollo

I am using postsConnection query for infinite scroll. It contains variables like after.
After doing an upvote mutation, I want to refetchQueries... like this 👇
const upvote = await client.mutate({
mutation: UPVOTE_MUTATION,
variables: {
postId: this.props.post.id
},
refetchQueries: [
{ query: POST_AUTHOR_QUERY }
]
})
Above code gives error because POST_AUTHOR_QUERY accepts few variables. Here's that query 👇
export const POST_AUTHOR_QUERY = gql`
query POST_AUTHOR_QUERY($authorUsername: String! $orderBy: PostOrderByInput $after: String){
postsAuthorConnection(authorUsername: $authorUsername orderBy: $orderBy after: $after) {
....
}
}
I do not want to add variables manually. Variables are already stored in the cache. How do I reuse them while using refetchQueries???
Here are a few resources I have read about this issue 👇
https://github.com/apollographql/react-apollo/issues/817
https://github.com/apollographql/apollo-client/issues/1900
As mentioned in the issue you linked, you should be able to do the following:
import { getOperationName } from 'apollo-link'
const upvote = await client.mutate({
// other options
refetchQueries={[getOperationName(POST_AUTHOR_QUERY)]}
})
From the docs:
Please note that if you call refetchQueries with an array of strings, then Apollo Client will look for any previously called queries that have the same names as the provided strings. It will then refetch those queries with their current variables.
getOperationName simply parses the document you pass it and extracts the operation name from it. You can, of course, provide the operation name yourself as a string instead, but this way avoids issues if the operation name changes in the future or you fat finger it.
If you don't want to pull in apollo-link, you can also get this via the base graphql package (note that I use optional chaining for convenience:
import { getOperationAST } from 'graphql';
const operationName = getOperationAST(POST_AUTHOR_QUERY)?.name?.value;
// Note that this could technically return `undefined`
const upvote = await client.mutate({
mutation: UPVOTE_MUTATION,
variables: {
postId: this.props.post.id
},
refetchQueries: [operationName]
})

Categories