How to set only ONE element in an array in firebase - javascript

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.

Related

forEach not a function when index starts at anything but 0

When inside an object there isn't an index that starts with 0, it returns:
TypeError: data.forEach is not a function
This is how the database looks:
If I add an object with the index 0 to the database, like so (my formatting doesn't matter, this is just to illustrate the hierarchy):
0: {
email: "testmail",
uid: "testuid"
}
Suddenly the forEach function works and also retrieves the users with index 3 and 4. How can I make the forEach loop start at index 3 for example? Or is there a different method that I should be using instead? My code:
useEffect(() => {
if(props.klasData.gebruikers !== undefined) {
var data = props.klasData.gebruikers;
data.forEach(function (user) {
if(!emails.hasOwnProperty(user.email)) {
addEmail(oldArray => [...oldArray, user.email]);
}
setPending(false)
})
}
}, []);
Edit
props.klasData.gebruikers returns all keys within the "gebruikers" object with their children.
It looks like your data is being interpreted as an array by the Firebase SDK, since it starts with sequential numeric keys. If you print the value of your gebruikers snapshot, you'll see that it's:
[null, null, null, {email: "test", uid: "test"}, {email: "wouter#...", uid: "..."}]
These null values are added by the Firebase SDK to turn the keys into a proper array.
If you want to keep the Firebase SDK from converting the data into an array, prefix the keys with a non-numeric character. For example:
"key2": {email: "test", uid: "test"},
"key3": {email: "wouter#...", uid: "..."}
In general, it is more idiomatic to use the UID of the users as the keys in a collection of users. That way, you won't need to query for the UID, and you're automatically guaranteed that each user/UID can only be present in the collection one.
I changed the database like you can see here, as Frank van Puffelen suggested. As Frank also predicted, the root of my problem was coming from the function I didn't post.
By transforming all the indexes of UIDs I was fetching from the database to sequential numeric keys, I managed to get the forEach function working. I did this using users = Object.values(users).filter((el) => {return el != null}). The full effect hook can be found below:
useEffect(() => {
var refCodes = firebase.database().ref("userdata/" + currentUser.uid + "/docentCodes").orderByKey();
refCodes.once("value").then(function (snapshotCodes) {
snapshotCodes.val().forEach(function (code) {
var refCodeData = firebase.database().ref("klassencodes/" + code).orderByKey();
refCodeData.once("value").then(function (snapshotCodeData) {
var users = snapshotCodeData.val().gebruikers;
users = Object.values(users).filter((el) => {return el != null})
if(snapshotCodeData.val() !== null) {
setUsercount(oldArray => [...oldArray, users.length]);
setKlasData(oldArray => [...oldArray, snapshotCodeData.val()]);
setUserdata(oldArray => [...oldArray, users]);
addCode(oldArray => [...oldArray, code])
}
setPending(false);
})
})
})
}, []);
In the function where this useEffect is used, I added const [userdata, setUserdata] = React.useState([]); to acommodate this new information stripped down from indexes of UIDs to indexes made of numeric keys. This userdata is exported to another function, which has the effect hook as stated in the original question. I changed this up to be:
useEffect(() => {
if(props.userData !== undefined) {
var data = props.userData;
if(data !== undefined) {
data.forEach(function (user) {
if(!emails.hasOwnProperty(user.email)) {
addEmail(oldArray => [...oldArray, user.email]);
addUID(oldArray => [...oldArray, Object.keys(props.klasData.gebruikers)]);
}
setPending(false)
})
}
}
}, []);
Summary
In retrospect, I should've gone with a seperate const for just the userdata (snapshotCodeData.val().gebruikers), seperate from the other data returned from the snapshot (snapshotCodeData.val()).
I hope this may help you. The golden line of code is users = Object.values(users).filter((el) => {return el != null}).

Angular Firestore - search for and update single document

I'm new to web (and everything asynchronous). I'm trying to complete a two step process in an Angular + Firebase app:
Query a Firestore collection to find the ID of a document that matches a filter (name == 'theName').
Use the ID to then update the document.
I'm coming from the embedded world where I can do something like this (context of the app - I'm trying to keep track of results in a combat robotics competition).
// Update the winning robot's doc with results
// Find the ID of the robot we're looking for (ex. winningRobotName = "Some Name")
this.firestore.collection('robots', ref => ref.where('name', '==', winningRobotName)).snapshotChanges()
.subscribe(data => {
this.bots = data.map(e => {
return {
id: e.payload.doc.id,
name: e.payload.doc.data()['name'],
// other fields
} as Robot;
})
});
let robot = this.bots[0]; <--------- This doesn't work because I reach here before the above call returns.
// Update this robot with new fields
this.firestore.doc('robots/' + robot.id)
.update({
fightCount : robot.fightCount + 1,
winCount : robot.winCount + 1,
// other updates
});
How does one wait for one subscription to return before executing another command? Do you nest the subscriptions? Is there something really basic I just don't know about yet?
Thanks.
I don't think AngularFire helps you here, and would instead do this directly on the regular JavaScript SDK:
ref.where('name', '==', winningRobotName))
.get()
.then((snapshots) => {
snapshots.forEach((doc) =>
doc.ref.update({
id: doc.id,
name: doc.data().name
})
})
})
I'm not exactly sure what name: doc.data().name is supposed to do (as it's an identity operation, delivering the same result as its input), but left it in just in case this matters to you.

Firebase Functions only sets first value in Firebase Realtime Database

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);
});

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 update Array field?

Im using Firebase Firestore and want to update an array field under a userprofile with the latest chat thread's id.. Im guessing that I have to pull the entire array (if it exists) from the chat node under that user, then I need to append the new id (if it doesnt exist) and update the array.. It works when theres only 1 value in the array then it fails after that with the following error:
Transaction failed: { Error: Cannot convert an array value in an array value.
at /user_code/node_modules/firebase-admin/node_modules/grpc/src/node/src/client.js:554:15 code: 3, metadata: Metadata { _internal_repr: {} } }
and here is my firebase cloud function, can anyone tell me where im going wrong ?
exports.updateMessages = functions.firestore.document('messages/{messageId}/conversation/{msgkey}').onCreate( (event) => {
/// console.log('function started');
const messagePayload = event.data.data();
const userA = messagePayload.userA;
const userB = messagePayload.userB;
// console.log("userA " + userA);
// console.log("userB " + userB);
// console.log("messagePayload " + JSON.stringify(messagePayload, null, 2) );
const sfDocRef = admin.firestore().doc(`users/${userB}`);
return admin.firestore().runTransaction( (transaction) => {
return transaction.get(sfDocRef).then( (sfDoc) => {
const array = [];
array.push(...[event.params.messageId, sfDoc.get('chats') ]);
transaction.update(sfDocRef, { chats: array } );
});
}).then( () => {
console.log("Transaction successfully committed!");
}).catch( (error) => {
console.log("Transaction failed: ", error);
});
});
You're nesting arrays in your code here:
const array = [];
array.push(...[event.params.messageId, sfDoc.get('chats') ]);
This leads to an array with two values, the first one being the new messageId and the second value contains an array all of your previous values, e.g.
[ "new message id", ["previous id", "older id"] ]
This type of nested array is something that Firestore (apparently) doesn't allow to be stored.
The solution is simple:
const array = [event.params.messageId, ...sfDoc.get('chats')];
The fact that you have to first load the array to then add a single element to it is one of reasons Firebasers recommend not storing data in arrays. Your current data looks like it'd be better off as a set, as shown in the Firestore documenation:
{
"new message id": true,
"previous id": true,
"older id": true
}
That way adding a chat ID is as simple as:
sfDocRef.update({ "chats."+event.params.messageId, true })
I have looked further into the matter, and I would follow the advice that Frank gave you in his post; allocate the data in collections rather than with arrays as they have greater versatility for Firebase 1. Researching under the examples listed in the Firebase website looking for anything related to a chat, I’ve found the data structure and code for messages that are used by Firechat as they might be of use for you.
In the source code, they use a collection for the their message-id -userId pair with the following topology 2 :
The exact way how the saving is executed at the repository is 3 :
It executes an append of the message into the Room-id collection. Instead of this structure, you could use an userID - messageID pair as it might fit you better.

Categories

Resources