Adding data to nested collection firebase cloud firestore - reactjs

Right now my firebase cloud firestore looks like this:
const db = fire.firestore();
db.collection("groupFavs/"+groupID+ "/"+currentUserUID).add({favs: likedData})
The code above creates a collection called groupFavs, a document called groupID and another collection with the current users id. In the image I have above, it keeps creating documents for the users likes when i just want the currentuser id to only have one list in it which will be all their liked data. But instead it just keeps making multiple of them and I think its because of .add
I've tried using set but that wont work. Where am I going wrong?
db.collection("groupFavs2")
.doc(groupID).collection(currentUserUID)
.set({
favs: likedData
})
I basically want to do something like this above but my logic is off in regards to the nested collection because what im calling above does not work

You can circumvent the string concatenation as well as the nested .collection .doc chain by using string interpolation as follows:
const collectionRef = db.collection(`groupFavs/${groupID}/${currentUserUID}`)
collectionRef.add({favs: likedData})
In regards to the multiple documents, you are adding a new document under the sub collection (matching the users id). Ideally this would look something like this:
Group Favs
1st UserId
Like 1 doc
Like 2 doc
2st UserId
Like 1 doc
Like 2 doc
Where the like doc can contain information related to the like such as image or liked boolean property.
If instead what you would like to do is keep a list of the users likes under their doc you can do it as follows:
const docRef= db.doc(`groupFavs/${groupID}/${currentUserUID}`)
const newLikeData = {
likes: firestore.FieldValue.arrayUnion(<your_like_information>)
}
docRef.set({favs: likedData}, {merge:true})
arrayUnion will append whatever data is in <your_like_information>.
Alternatively, you could query for the current likes list under the users doc, manually append the information and pass the entire object to a update or setDoc with merge: true.

Related

Get Firestore collection and sub-collection document data together

I have the following Firestore database structure in my Ionic 5 app.
Book(collection)
{bookID}(document with book fields)
Like (sub-collection)
{userID} (document name as user ID with fields)
Book collection has documentes and each document has a Like sub-collection. The document names of Like collection are user IDs who liked the book.
I am trying to do a query to get the latest books and at the same time trying to get the document from Like sub-collection to check if I have liked it.
async getBook(coll) {
snap = await this.afs.collection('Book').ref
.orderBy('createdDate', "desc")
.limit(10).get();
snap.docs.map(x => {
const data = x.data();
coll.push({
key: x.id,
data: data.data(),
like: this.getMyReaction(x.id)
});
}
async getMyReaction(key) {
const res = await this.afs.doc('Book/myUserID').ref.get();
if(res.exists) {
return res.data();
} else {
return 'notFound';
}
}
What I am doing here is calling the method getMyReaction() with each book ID and storing the promise in the like field. Later, I am reading the like value with async pipe in the HTML. This code is working perfectly but there is a little delay to get the like value as the promise is taking time to get resolved. Is there a solution to get sub-collection value at the same time I am getting the collection value?
Is there a solution to get sub-collection value at the same time I am getting the collection value?
Not without restructuring your data. Firestore queries can only consider documents in a single collection. The only exception to that is collection group queries, which lets you consider documents among all collections with the exact same name. What you're doing right now to "join" these two collections is probably about as effective as you'll get.
The only way you can turn this into a single query is by having another collection with the data pre-merged from the other two collections. This is actually kind of common on nosql databases, and is referred to as denormalization. But it's entirely up to you to decide if that's right for your use case.

Firebase cloud function not updating record

imagine this scenario:
You have a list with lets say 100 items, a user favorites 12 items from the list.
The user now wants these 12 items displayed in a new list with the up to date data (if data changes from the original list/node)
What would be the best way to accomplish this without having to denormalize all of the data for each item?
is there a way to orderByChild().equalTo(multiple items)
(the multiple items being the favorited items)
Currently, I am successfully displaying the favorites in a new list but I am pushing all the data to the users node with the favorites, problem is when i change data, their data from favorites wont change.
note - these changes are made manually in the db and cannot be changed by the user (meta data that can potentially change)
UPDATE
I'm trying to achieve this now with cloud functions. I am trying to update the item but I can't seem to get it working.
Here is my code:
exports.itemUpdate = functions.database
.ref('/items/{itemId}')
.onUpdate((change, context) => {
const before = change.before.val(); // DataSnapshot after the change
const after = change.after.val(); // DataSnapshot after the change
console.log(after);
if (before.effects === after.effects) {
console.log('effects didnt change')
return null;
}
const ref = admin.database().ref('users')
.orderByChild('likedItems')
.equalTo(before.title);
return ref.update(after);
});
I'm not to sure what I am doing wrong here.
Cheers!
There isn't a way to do multiple items in the orderByChild, denormalising in NoSQL is fine its just about keeping it in sync like you mention. I would recommend using a Cloud Function which will help you to keep things in sync.
Another option is to use Firestore instead of the Realtime Database as it has better querying capabilities where you could store a users id in the document and using an array contains filter you could get all the users posts.
The below is the start of a Cloud function for a realtime database trigger for an update of an item.
exports.itemUpdate = functions.database.ref('/items/{itemId}')
.onUpdate((snap, context) => {
// Query your users node for matching items and update them
});

Firebase query with variables in Swift

I'm trying to find specific user images from my Firebase data set I'm naming the group users with unique display names as children and those children with keys of name, lowerCaseName, UID, and Image.
Is there anyway I can query through users as a variable in order to get to images?? I'm thinking http://firebase.com/users/%diplaynames/image but not sure.
What is your Firebase base url? You should be querying the path that looks something like:
https://<APP ID HERE>.firebaseio.com/
So you're initialization of Firebase should look like:
let fire = Firebase(url: "https://<APP ID HERE>.firebaseio.com/users/<DISPLAYNAME>/image")
Alternatively, you could do:
let fire = Firebase(url: "https://<APP ID HERE>.firebaseio.com")
let imageRef = fire.childByAppendingPath("users").childByAppendingPath("<DISPLAYNAME>).childByAppendingPath("image")
In Firebase, you cannot retrieve a subset of the properties of a node. Either a node is retrieved, or it isn't.
If you have a use-case where you need to retrieve just the images, you should store a top-level node that has just those images.
userImages
Charles: base64data
Zach: base64data
A couple of thoughts:
While there is nothing wrong with your structure, you may want to consider changing the node names from the users name to a firebase auto generated ID.
users
user_0
name: Charles
image: some image
user_1
name: Zaddy
image: another image
Sometimes data will change, so for example say Charles wants to be called Charlie. To make that change you would have to delete the Charles node*, re-add it, and then update every node that refers back to Charles. If you name it with an auto-generated id (or the uid; best practice) user_0, then just update the name: "Charlie" and everything else falls into place.
*The child name: and the node name can certainly be the same or different, so this is a situational example.
To get Charles' image (assuming it's base64 encoded) and you want to add it to an imageView
let ref = Firebase(url:"https://your_app.firebaseio.com/users")
ref.queryOrderedByChild("name").queryEqualToValue("Charles")
.observeSingleEventOfType(.ChildAdded, withBlock: { snapshot in
//snapshot will contain the child key/value pairs
let base64EncodedString = snapshot.value.objectForKey("image")
let imageData = NSData(base64EncodedString: base64EncodedString as! String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
let decodedImage = NSImage(data:imageData!)
self.myImageView.image = decodedImage
})

How to Fetch a set of Specific Keys in Firebase?

Say I'd like to fetch only items that contains keys: "-Ju2-oZ8sJIES8_shkTv", "-Ju2-zGVMuX9tMGfySko", and "-Ju202XUwybotkDPloeo".
var items = new Firebase("https://hello-cambodia.firebaseio.com/items");
items.orderByKey().equalTo("-Ju2-gVQbXNgxMlojo-T").once('value', function(snap1){
items.orderByKey().equalTo("-Ju2-zGVMuX9tMGfySko").once('value', function(snap2){
items.orderByKey().equalTo("-Ju202XUwybotkDPloeo").once('value', function(snap3){
console.log(snap1.val());
console.log(snap2.val());
console.log(snap3.val());
})
})
});
I don't feel that this is the right way to fetch the items, especially, when I have 1000 keys over to fetch from.
If possible, I really hope for something where I can give a set of array
like
var itemKeys = ["-Ju2-gVQbXNgxMlojo-T","-Ju2-zGVMuX9tMGfySko", "-Ju202XUwybotkDPloeo"];
var items = new Firebase("https://hello-cambodia.firebaseio.com/items");
items.orderByKey().equalTo(itemKeys).once('value', function(snap){
console.log(snap.val());
});
Any suggestions would be appreciated.
Thanks
Doing this:
items.orderByKey().equalTo("-Ju2-gVQbXNgxMlojo-T")
Gives exactly the same result as:
items.child("-Ju2-gVQbXNgxMlojo-T")
But the latter is not only more readable, it will also prevent the need for scanning indexes.
But what you have to answer is why want to select these three items? Is it because they all have the same status? Because they fell into a specific date range? Because the user selected them in a list? As soon as you can identify the reason for selecting these three items, you can look to convert the selection into a query. E.g.
var recentItems = ref.orderByChild("createdTimestamp")
.startAt(Date.now() - 24*60*60*1000)
.endAt(Date.now());
recentItems.on('child_added'...
This query would give you the items of the past day, if you had a field with the timestamp.
You can use Firebase child. For example,
var currFirebaseRoom = new Firebase(yourFirebaseURL)
var userRef = currFirebaseRoom.child('users');
Now you can access this child with
userRef.on('value', function(userSnapshot) {
//your code
}
You generally should not be access things using the Firebase keys. Create a child called data and put all your values there and then you can access them through that child reference.

AppEngine Datastore get entities that have ALL items in list property

I want to implement some kind of tagging functionality to my app. I want to do something like...
class Item(db.Model):
name = db.StringProperty()
tags = db.ListProperty(str)
Suppose I get a search that have 2 or more tags. Eg. "restaurant" and "mexican".
Now, I want to get Items that have ALL, in this case 2, given tags.
How do I do that? Or is there a better way to implement what I want?
I believe you want tags to be stored as 'db.ListProperty(db.Category)' and then query them with something like:
return db.Query(Item)\
.filter('tags = ', expected_tag1)\
.filter('tags = ', expected_tag2)\
.order('name')\
.fetch(256)
(Unfortunately I can't find any good documentation for the db.Category type. So I cannot definitively say this is the right way to go.) Also note, that in order to create a db.Category you need to use:
new_item.tags.append(db.Category(unicode(new_tag_text)))
use db.ListProperty(db.Key) instead,which stores a list of entity's keys.
models:
class Profile(db.Model):
data_list=db.ListProperty(db.Key)
class Data(db.Model):
name=db.StringProperty()
views:
prof=Profile()
data=Data.gql("")#The Data entities you want to fetch
for data in data:
prof.data_list.append(data)
/// Here data_list stores the keys of Data entity
Data.get(prof.data_list) will get all the Data entities whose key are in the data_list attribute

Resources