Firebase Functions only sets first value in Firebase Realtime Database - javascript

I'm trying to save some data to all subscribers of a group whenever a new event is added to their group. It seems to work but somehow firebase functions only saves the first value in my firebase realtime database.
Like you see in my code I'm doing this inside a forEach and also without a forEach. Depending on whether it's a single-event or a group-event. Outside the forEach it works perfectly and all the data is saved. However inside the forEach it only saves the 'title'.
In the console.log() it shows me all the right values in the firebase functions console but still it won't appear in the realtime database.
exports.saveNewEventToUsers = functions.database.ref('events/{groupId}/{eventId}').onCreate((snap, context) => {
const root = snap.ref.root;
// removed some consts for this example
// if the groupId equals the creator it's a single-event ELSE a group-event
if (groupId === data.creator) {
singleEvent = true;
return root.child(`userEvents/${groupId}/${eventId}`).set({ title, timestamp, singleEvent, creator, eventId, groupId });
} else {
return root.child(`groups/${groupId}/selected`).once('value', snapshot => {
snapshot.forEach(user => {
console.log(title, timestamp, singleEvent, creator, eventId, groupId);
return root.child(`userEvents/${user.key}/${eventId}`).set({ title, timestamp, creator, eventId, groupId });
}); // forEach
});
}
})
On the image you see that for single-events it works as expected but at group-events it only saves the title.
Anybody any idea what causes the problem? What am I doing wrong? Thank you very much in advance for your help!

You're mixing callbacks and promises, so the function is terminating before you expect it to. Try something more like:
return root.child(`groups/${groupId}/selected`).once('value').then(snapshot => {
const sets = [];
snapshot.forEach(user => {
console.log(title, timestamp, singleEvent, creator, eventId, groupId);
sets.push(root.child(`userEvents/${user.key}/${eventId}`).set({ title, timestamp, creator, eventId, groupId }));
}); // forEach
return Promise.all(sets);
});

Related

How to properly use Firestore's serverTimestamp to set the value of document thats being subscribed to?

Overview/Environment:
a react-native project v0.61.5
using react-native-firebase package
using actions to populate redux state, display firestore data through props
Goal:
Listen to collection of documents
use Firestore's FieldValue.serverTimestamp() to set time value of a document in said collection
use serverTimestamp's toMillis() function inside a snapshot listener
Observations/Errors:
when creating a document in said collection, the document gets created fine, and displays fine
while the doc/time value is created, the applications crashes due to the call to doc.get('time').toMillis() which is inside the snapshot listener: TypeError: null is not an object (evaluating 'doc.get('time').toMillis()')
So far I've tried all the suggestions noted here: Why is Firestore's 'doc.get('time').toMillis' producing a null Type Error?
Nothing seems to resolve this crash.
here's the snapshot listener:
.onSnapshot({ includeMetadataChanges: true }, (querySnapshot) => {
if (querySnapshot.metadata.fromCache && querySnapshot.metadata.hasPendingWrites) {
// ignore cache snapshots where new data is being written
return;
}
const messages = [];
querySnapshot.forEach((doc) => {
const estimateTimestamps = { serverTimestamps: 'estimate' }
const msg = doc.data();
msg.docId = doc.id;
msg.time = doc.get('time', estimateTimestamps).toMillis();
const timestamp = doc.get('time', estimateTimestamps);
if (timestamp) {
msg.time = timestamp.toMillis();
} else {
debugger
console.error(doc.id + ' is missing "time" field!');
}
messages.push(msg);
});
dispatch({ type: types.LOAD_MSGS, payload: messages });
resolve();
});
Here's how document is created:
const addMsg = (msg, userConvos) => {
return firebase.firestore().collection('messages').add({
time: firebase.firestore.FieldValue.serverTimestamp(),
sender: msg.sender,
read: false,
userConvos: [userConvos.sender, userConvos.receiver],
content: {
type: 'msg',
data: msg.text
}
});
};
I understand the value may be null fora small amount of time, I need a way to prevent the app from crashing during that period.
The error is pointing you to this code:
doc.get('time').toMillis()
It's saying that doc.get('time') returns null, and therefore, you can't call toMillis() on that.
The answer to the question you linked to explains exactly why that is. If it's still unclear, I suggest reading it again. The timestamp will simply be null if the event that a server timestamp has not reached the server.
Perhaps you meant to check if the timestamp is null like this, without calling toMillis():
msg.isPending = doc.get('time') === null;
After #DougStevenson helped me understand. Somewhat confusing but its important to understand the listener is constantly running, so once the Time value is available it will be set, so no real performance issues. I reformulated my approach to this, its working:
querySnapshot.forEach((doc) => {
const estimateTimestamps = { serverTimestamps: 'estimate' }
const msg = doc.data();
msg.docId = doc.id;
msg.time = doc.get('time', estimateTimestamps).toMillis();
const timestamp = doc.get('time', estimateTimestamps)
if (doc.get('time') !== null) {
msg.time = doc.get('time').toMillis()
}
messages.push(msg);
});

Returning updated object after update to database instead of count

I am making an update to an entry in a database of food trucks. This is what my model looks like for this particulate update:
function editTruck(changes, id) {
return db('trucks')
.where({ id })
.update(changes)
.then(id => {
return findTruckById(id);
})
}
I want to return the actual updated entry in object form. As you can see above, I tried to accomplish this by drawing on another method in the model called findTruckById. This is what findTruckById looks like:
function findTruckById(id) {
return db('trucks')
.select('id', 'name', 'image', 'operator_id', 'cuisine_type', 'physical_address')
.where({ id })
.first()
}
This doesn't work because the response returned from the editTruck method is just a count of the entries in the database that were updated, which is 1, as opposed to an id or an array of ids for the entries. Therefore, it always just returns 1 and running 1 inside findTruckById returns an object equivalent to the entry in the trucks database with id 1, which is definitely not what we want. How can I solve this problem?
You already have the ID, you don't need the "update" to return it.
function editTruck(changes, id) {
return db('trucks')
.where({ id })
.update(changes)
.then(() => { // you already have the id, just use the `id` that is already in the scope
return findTruckById(id);
})
}

How to set only ONE element in an array in firebase

I have an array saved on my firebase, like this:
matches:[ {match:{id:1,data:...}}]
I want to save just one item on firebase at this array. For example, my match have id:32. I want to find it on the array saved in firebase, and change it.
Im trying to make this. But Im thinking that this is VERY UGLY to make a request to the firebase, copy the array, and save the entire array again.
const ref = `/users/${currentUser.uid}/matches`;
var list = [];
firebase.database().ref(ref).on('value', function (snap) { list = snap.val(); });
if (list.length > 0) {
const indexToUpdate = list.findIndex(k => k.name == match.name)
if (indexToUpdate >= 0) {
list[indexToUpdate] = match;
return dispatch => {
firebase
.database()
.ref(`/users/${currentUser.uid}/matches`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
};
}
}
Any light?
This line of code:
const indexToUpdate = list.findIndex(k => k.name == match.name)
Is a dead giveaway that your data structure is not ideal.
Consider storing the matches under their name, prefixing it with a string to prevent the array coercion that Kenneth mentioned. So:
matches
match1
date: "2018-06-14..."
...
match2
date: "2018-06-16..."
...
With this you can look up the node for a match without needing a query. It also prevents using an array, which is an anti-pattern in Firebase precisely because of the reason you mention.
For more on this, see the classic Firebase blog post on Best Practices: Arrays in Firebase.
Firebase stores arrays as object and converts it back to array when it comes back to the client if the keys are ordered numerically correctly
But basically it should be able to work the same where you make your path up to the object you want to update.
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/${indexToUpdate}`)
.set(match)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
You need to reference the ID of the item you are trying to set by ID in the URL
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/32`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
Also, use snap.key to get the ID you need if index isn't working.

Accessing Firebase data in a node with JS

So I have an object being returned from Firebase that looks like this:
{key: {name: "test", email: "test", id: "test"}}
How can I get the id out of this object?
If I do returnItem I get that object, so I tried to do returnItem[0] but it's not an array, and I've tried (Object.keys(tempSnap) but that just gives me the key not the object inside it.
This is my current code:
export function sendInvitation(email) {
firebaseRef.database().ref().child('users').orderByChild('email').equalTo(email).on("value", function(snapshot) {
let tempSnap = snapshot.val();
if(tempSnap != null) {
console.log(tempSnap);
}
});
return dispatch => firebaseRef.database().ref(`${userID}/invites`).push("This is a test Message!");
}
This is what it outputs:
Help would be awesome :D
If you already know id and it's a literal, then it's a matter of returnItem.id.
If you already know id and it's a variable, then it's returnItem[id].
If you don't know the keys and want to print all keys and their values, it's:
Object.keys(returnItem).forEach(function(key) {
console.log(key, returnItem[key]);
});
Update
Your new code shows the problem. When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result. Your callback needs to handle the fact that it gets a list by looping over the results with snapshot.forEach():
firebaseRef.database().ref().child('users').orderByChild('email').equalTo(email).on("value", function(snapshot) {
snapshot.forEach(function(child) {
let tempSnap = child.val();
console.log(tempSnap);
});
});
Try this:
firebaseRef.database().ref().child('users').orderByChild('email').equalTo(email).on("value", function(snapshot) {
snapshot.forEach(function(child) {
let keys=child.key;
let ids=child.val().id;
)};
)};
you have:
users
keyid
email:email
name:yourname
id: test

How to save the objects uid when I push the data in firebase database list, using javascript

I want to save the uid when I push new data in Firebase database. This is possible not for auth users but for data objects.
For example, I want this object schema:
"-Kdsfdsg555555fdsgfsdgfs" : { <------- I want this id
Id : "Kdsfdsg555555fdsgfsdgfs", <--- How to put that inside
name : "A",
time_updated : "6/6/2017"
}
Is any way how to get this id and pushed inside the object?
My code is as follows:
categories: FirebaseListObservable<any[]>;
this.categories = this.db.list('/categories') as FirebaseListObservable<Category[]>;
addCategory(category: any) {
return this.categories.push(category).then( snap => {
this.openSnackBar('New category has been added', 'ok');
}).catch(error => {
this.openSnackBar(error.message, 'ok');
});
}
There are two ways to use Firebase's push() method:
By passing in an argument, it will generate a new location and store the argument there.
By passing in no arguments, it will just generate a new location.
You can use the second way of using push() to just get the new location or its key:
addCategory(category: any) {
var newRef = this.categories.push();
category.Id = newRef.key;
newRef.set(category).then( snap => {
this.openSnackBar('New category has been added', 'ok');
}).catch(error => {
this.openSnackBar(error.message, 'ok');
});
}
But note that it's typically an anti-pattern to store the key of an item inside that item itself too.

Categories

Resources