Retrieve a dynamic value from a subcollection


#1

I would like to be able to retrieve a dynamic value (scores) associated with each one of the elements of my collection by each one of the users. I have already figured out how to estructure things, so right now I have:

\collectionOfItems {
     Item1{
           name: Lorem
           link: ipsum
           scores{
                  user1Uid{
                           value: 69
                           date: today
                  }
           }
     }
      Item2{
            name: Lorem2
            link: ipsum2
            scores{
                   user2Uid{
                            value: 43
                            date: yesterday
                   }
            }
     }
}

\collectionOfUsers {
     user1Uid{
              name: John
              email: john@smith.com
     }
    user2Uid{
              name: mary
              email: mary@johnson.com
     }
}

So I have placed a subcollection within each item named “scores” where each document will have the specific UID of the user saving that data (just the value and the date). I know I can point to that document based on the value in the ds_SlotUserId. But how can I set the dynamic value of each item? Because I have a general sheet with of the information for each item in my collection, but the values inside a subcollection won’t appear there. I guess I have to create an additional data sheet pointing to those values inside “score”, but those values are different depending on the item and the user. How do I do that in React Studio?


#2

Check this: https://www.youtube.com/watch?v=1OZFsgHa3fE&list=PLKrYy47a9BRleRGnLAarv5zkf_2eDUrh6

It’s a chat app but similar structure with collection (chatrooms) and sub collection (chat messages). There are two data sheets; one for Chatrooms and one for selected Chatroom’s Chat messages (this data sheet updates based on the chatroom id data slot value).

P.S. Adding data into sub collection needs to be edited in list or details view. Otherwise it will not work in RS project.

P.S.S To my knowledge you cannot easily list documents and sub collections for each document. Showing one sub collection for selected document in Details view is trivial but having “list inside list” is not possible at least in React Studio (it would also create lot’s of reads in Firestore). Read more e.g. here: http://www.lukasjakob.com/how-to-get-firestore-collection-data-with-sub-collections-in-angular/


#3

Thank you so much for your answer. I see it is a little bit tricky to retrieve the information within a subcollection. Maybe there is a better approach to what I want. I read a couple of articles and saw several videos about noSQL and I created a collection in the root folder named “scores”. Inside that collection, each document has the same ID as the UID for each user and those documents will have fields with the ID of the item and the score they have achieved. It would be something like this:

\collectionOfItems {
     Item1{
           name: Lorem
           link: ipsum
           }
     }
      Item2{
            name: Lorem2
            link: ipsum2
            }
     }
}

\collectionOfUsers {
     user1Uid{
              name: John
              email: john@smith.com
     }
    user2Uid{
              name: mary
              email: mary@johnson.com
     }
}
\scores{
      user1Uid{
              Item1: 68
              Item2: 72
     }
      user2Uid{
              Item1: 93
              Item2: 32
     }
}

So I was wondering if this would work, and as I have a component that shows all the information from each item, I would like that it also shows the score from the user that is logged in at that moment. How could I achieve this?

Thank you.


#4

Well you just need to create Data sheet and query which queries the scores based on user id and use this Data sheet as a data source for the screen.

Here’s how I quickly tested this. I added collection called scores and data sheet which queries the collection is then used as data source for the screen and finally for the component.


#5

Thank you. I understand but I believe my scenario is a little bit tricky. I want the score to appear in a screen that already depends from a different data sheet.

I have a data sheet with all the items within my collection. Each item is shown in a different card (component) where the image, name, label, etc. are on the main datasheet. What I want is that the score for each item that the logged in user has achieved appears in its item. And that relationship is what I am not able to do. I guess it is me not understanding exactly how to link the dots.


#6

Ok then this is more about how to model the data in Firebase. Basibally it’s ”list in a list” which can be achieved by Denormalizing the data into many places with cloud functions or e.g. creating cloud function which returns data based on some ID. This would probably also require that you fill the ”inner” list with the script in the List element’s ”Items array”.

Anyway Firestore has new video (five videos) series about this: https://youtu.be/G04w_k3TPTs


#7

I am back to this goal and trying hard to achieve it. I have been watching the video series, reading some documentation and trying a few things in my project but I still don’t get to press the right button. So this is my document’s template in the project:

All of the fields in the card comes from one data sheet that queries the document’s info in the database. The only data that is dynamic (besides the favorite icon which I have already resolve) is the score. That values changes depending on the user and the specific document. I have understood that I won’t be able to achieve it if I place the scores as a subcollection within each document, as it is not possible to show a “list in a list”. Being so, I created a “users” collection in the root folder of my database where I placed the favorites and I can easily place the scores there and create fields where the document_key is the exact same one as the document and the result is the score.

So I have a dataSheet with a query that goes to a different collection depending on the userID and shows the document_key and the score:

The document_key is exactly the same one that I have in the data for each document in the main collection. In the next screenshot it is the first row:

When I go to the component and link the field text to this data sheet and preview the app, it doesn’t show the score. I hope I explained well enough what I want to achieve.

Thank you.


#8

So you have a separate screen for showing the score? I dont see any problem in that and it should work. Showing the score from separate document for each row is not possible (at least) in Studio unless you do it with some kind of custom script which makes a separate quorey and crates new array for each item in the main list.

I’m not sure if I understood what you try to achieve but if you check the code which Studio generated you probably get better idea how data sheets work. Basically data sheet is array which can be populated only once for each screen. If you need separate array for each list item then you need to somehow do that in the code.


#9

Thank you again for your answer. If it is not possible directly in React Studio, I am afraid I won’t be able to get it done by myself yet as I am just learning the fundamentals of Javascript. I am going to open the code generated for the component and the screen but I know I still have much to learn from both Javascript and React. I dedicate some time everyday to read, watch videos and try the code and I am developing this project in React Studio which allows me to do it even though I haven’t learnt yet how to get it done with code.

I will explain better the project and see if that clears what I want to achieve. The project is basically a menu that show a collection of items (songs to be played by the user). That is the main collection and the main data Sheet that contains all data related with each one of the documents (songs) contained in that collection.

Then, there is some dynamic values that will depend on the user. I already solved how to query the users favorites. The most complicated is the score. As the student will play some of the songs (items in the collection) the obtained score will be associated with the user ID and the specific document or song. So the main card for each document has to show the static and generic data (contained in the main data Sheet) and a dynamic value that has to linked to the user ID and the document or item in the main collection.

In this first approach, I will be adding those scores manually in the database, so the menu just have to render and show those values when the student opens the app with his/her user.

I hope I explained it better this time. Thank you.


#10

Hello again,

I have been taking a look at “Code glance” to see if I can figure out how to make it work. I am guessing the place where I should change something would be:

let row = this.props.dataSheetRow || {
“image”: this.props.image,
“name”: this.props.name,
“level”: this.props.level,
“preloadLink”: this.props.preloadLink,
“sheetMusic”: this.props.sheetMusic,
“score”: this.props.score,
};
row = { …row,
sheetMusic: this.props.dataSheetRow.sheetMusic,
document_key: this.props.dataSheetRow.document_key,
level: this.props.dataSheetRow.level,
image: this.props.dataSheetRow.image,
name: this.props.dataSheetRow.name,
preloadLink: this.props.dataSheetRow.preloadLink,
};
if (this.props.dataSheetId === dataSheet.id) {
this.props.appActions.updateInDataSheet(‘favorites’, row);
} else {
this.props.appActions.addToDataSheet(‘favorites’, row);
}
}

I don’t know where the problem might be. I have tried to change the data source of the score field to the “document_key” in the same dataSheet that I have the score and that trigger something. Even though in that dataSheet (scores) I have only one row for one document, the collection shows the document_key for each document. I am guessing that as the name is exactly as it is in the main collection it behaves that way.

Given that, I tried creating an empty field named “score” in the main collection, to see if that would trigger showing the number in the “score” data sheet, but it didn’t work. What should I do?


#11

This is similar structure like having likes or comments for any social feed posts. Big projects are probably doing this storing feed collection for each user, it’s just how no-sql world works. Lots of duplicate data but it makes reading the data much faster (which is the main use case for feeds). It would be rather impossible to maintain e.g. Facebook News feed in SQL-database, it just doesn’t scale.

Anyway there is no ready solution in Studio for your use case. Data sheets are sort “global” arrays and if you try to update score data sheet when listing songs then it would update the score data sheet and it would show same score for each song.

If you know enough code and understand how React screen renders you could do this by editing the song item’s code template (Script editor). It is not complex but definitely requires some basic understanding of how React works.

Logic would be that you retrieve scores with script in Component did mount and save the array in state (see image 1). Then in you can have list element and you can show the score array there by using the lists “Items array” script (see image 2)

In my example I’m able to generate UI with list of restaurants and every restaurant shows all reviews made by logged in user.

This is not that trivial use case and Studio is not designed for this complex logic in mind. Usually teams would have developers either create needed plugins or code the business logic after you’ve exported the project.

Here’s my test project in action: https://neonto.cloud/u/yourrestauranteviews

It should show only reviews which you’ve written in this app: https://neonto.cloud/u/restaurantsdemo/


#12

Thank you so much for helping me with this. I am almost done with this project. I won’t have many users or a great volume of data so I am not worried about denormalization or duplicating data. I will be using it to track my students progress from home as it just to use to complement my classes. I am a music teacher and learning to code since a short time.

I have been learning some Javascript these past days so I understand more or less your code snippet even though I wouldn’t be able to create it myself. Based on it I have made a version that would work for my component:

  componentDidMount() {
{{COMPONENT_DID_MOUNT_CODE}}
    
    //Scores Query starts
    const db = firebase.firestore();
    let userID=this.props.appActions.dataSlots['dsSlotUserId'];
    let preloadID=this.props.dataSheetRow.document_key;
    let scoresRef = db.collection('scores');
    let jsonArr=[];
    
    let query = scoresRef.where('preloadID', '==',preloadID).where('userID','==', userID).get()
    .then(snapshot => {
    if (snapshot.empty) = {
      console.log('No matching documents.');
      return;
    }
    snapshot.forEach(doc => {
    	console.log(doc.id, '=>', doc.data());
    	const data = { ...doc.data(), document_key: doc.id, document_ref: doc.ref };
    	jsonArr.push(data);
    });

	this.setState({"scores": jsonArr})
  })
  .catch(err => {
    console.log('Error getting documents', err);
  });
	//Scores Query ends
  
  }

I still have a few doubts. I am guessing that I have to create a collection named “scores” in the root level of my database. Each one of the document would have the userID as the name of each document within that collection. But I am not so sure how to store the preloadID and the score. What I have done is:

\scores\userID\ {preloadID1:87
preloadID2:64

}

So right now I have for each song a different value where the ID of the value is the songID and the result is the score. I am not so sure I did it fine as you are talking about arrays in your code snippet.

The other thing is that I need to display that value. I believe what you showed me from your project is how to input reviews.

I am sorry if some of my questions are obvious but most of the time is me not completely understanding the logic. I feel that I am getting close to my goal so I am quite happy.

Thank you!


#13

When I wrote about arrays I meant that the comments list on my example takes standard Javascript array as data source. It has nothing to do with Firebase.

I’m not 100% sure about your use case but why couldn’t you have Scores collection in root of your database. Each document in scores collection then could contain the score value and user id and song id?

In my example I showcased how one can show comments for each restaurant. I didn’t do anything regarding adding new documents.


#14

I am not sure about what the best way to store the scores to best fit how React Studio retrieve the value. I understand there are two options:

  1. I have already a “users” collection in the root level where I have documents with the “userID” as each document_key. Within each userdocument, I am already storing each user’s favorite documents in a subcollection named “favorites” where the selected documents by the user are just duplicated. I could create another subcollection named “scores” and then I could save the score. The path would be like this:

\users(collection)\userID(document)\scores(collection)\document_key(document)\score=78 (field = value)

  1. The other option is to create the scores collection in the root level. That way, the result would be something like this:

\scores(collection)\userID(document)\document_key=78 (field = value)

I am not sure which one is the best option so I am able to retrieve it in my component and display the value associated with the userID and the document_key the score is related to.

Thank you for your guidance. I really appreciate it.


#15

I would simply test it by adding scores collection into root of the db. Then just query that collection and you get the list of users scores for each song. If that works then you need to figure out if there is need to save the scores collection inside User document. Just keep it as simple as possible.

This is already pretty far from the Studio’s domain. Hope you find the solution.

P.S. I think @Marcos_Alencar might be able to help on this more. He has worked with quite similar project structure.


#16

Ok. Thank you for all the guidance. I will keep digging and trying to figure it out. I hope Marcos can bring some light over the issue.


#17

Just a heads up that I recorded a quick video regarding loading Reviews for each restaurant: https://youtu.be/aqHtOoWyF3I


#18

Thank you! I am following the steps. It is pretty much clear with the video. I already did the script but when I run it in the browser I get an error:

./src/PreloadCard.js  Line 24:  'firebase' is not defined  no-undefSearch for the keywords to learn more about each error.

#19

I probably forgot to mention that you need to import Firebase package to Component’s code. See screen shot below:


#20

Hi Pedro, I sending you a message. I’ll try to help you