Update a firestore registry when existing value


#1

I have in my project a component with a button that creates a document in my firestore database saving several values:

  • The userID of the user.
  • Specific documentKey of the document that the user has been working on.
  • User’s email.
  • Current date.
  • Obtained score in the document.

All of these are values are automatically stored under an aleatory documentKey.


Everything works great, but I have one thing that I would like to improve. Everytime that the user clicks the button, a new document is created in the database, but it wouldn’t be necessary to create a new one always. I would like that if there is an existing document with the same userID, documentKey but a diferent score, that by clicking the button instead of creating a new registry, that the existing score value would be set to the higher value.

The idea is to keep just the higher value in the score field for the given user and documentKey. Would that be possible? Here it is a screenshot of the datasheet where the values are stored.

Thank you!


#2

Maybe you could use the increment function for updating the value?

About increment:

To make it work you probably need to have two states for save button or probably the whole component: one for when there is no score in registry and one when there is a score in registry (just increment/decrease the score field).


#3

The issue that I see for my project with the increment feature is that I need to provide the field -which is “score”- and a specific value. But the value to be stored would be in localStorage in the browser.

The way my project works is that the user save in dataSlots several fields (userID, selected documentKey and user’s email) and the field score is obtained after some interactions and it is not a fixed value. Right now, every time the user create some of the score value, the component with the button to save it appear, and a new document is created in the database. What I wanted is not to create a new document but to edit the score value if it has the same userID and documentKey than an existing registry.

I hope I explained it correctly.


#4

If the save interaction is not made from list then I’m afraid that the code will always create a new document. You propably need to make a custom script which first checks if the score exists and then either adds or updates the value.


#5

Hi Pedro. You can do that either save in an Array of ids {whatever you want} that will be checked later if is null or undefined through a simple script using component states in React Studio.
As pointed by Antti, you can also use the increment method. Bellow, you find some scripts I’m using for a voting system I’m building. You can easily adapt it to your needs. Learning how to use these scripts is pretty easy and both will help you in other features you’ll need in your firebase project. Have fun! Best regards

First option.
In this example you will update or create with the userId data inside a collection of, in your case, “Scores” I guess. The merge: true will prevent saving another new doc.

var db = firebase.firestore()
var userId = this.props.appActions.dataSlots[‘ds_UserID’];
var ThreadsId = this.props.appActions.dataSlots[‘ds_ThreadID’];
var docRef = db.collection(Threads).doc(${ThreadsId}).collection(votes).doc(${userId})
docRef.get().then(function(doc) {
if (docRef.exists) {
let setDoc = docRef.update({userId: userId})
} else {
let setDoc = docRef.set({userId: userId}, {merge: true})
}
}).catch((fail) => {
});

Second option:


Arrays:
Here’s how you update
// Atomically add a userId to the “voted” array field.
docRef.update({voted: firebase.firestore.FieldValue.arrayUnion({userId})
});
Here’s how you delete
// Atomically remove the userId from “likes” array field.
docRef.update({
likes: firebase.firestore.FieldValue.arrayRemove({userId})
});


#6

Thank you so much both Marcos and Antti. I am going to try with the script you provided adapting it to my project. I am not sure if I will do it properly but I believe I understand the logic although I wouldn’t have been able to write it myself.

I will let you know when I try it. Thanks!


#7

Hello Marcos,

I am trying to adapt the script to my project but there are certain things I don’t know how to do it. This is how I change it:

var userId = this.props.appActions.dataSlots[‘ds_SlotUserId’];

No problem about this line. I just changed the name of the dataSlot as it is in my project.

var documentKey = this.props.appActions.dataSlots[‘ds_documentKey’];

I understand that ‘ThreadsID’ would be the documentKey of the document that the user has achieve some score, so I changed it for the documentKey dataSlot.

var docRef = db.collection(Threads).doc(${ThreadsId}).collection(votes).doc(${userId})
docRef.get().then(function(doc)

Here is where I am not sure what to do because you have in that line 2 database collections. In my project I just have a collection in the root of my database named ‘scores’. Inside it, I have one document per score. Each document contains several fields specifying the userID, the preloadID, date and obtained score. It would be something like this:

How would I do it?


#8

Hey Pedro. In your case, you don’t have any sub-collection, so you have do write it like this:

var documentKey = this.props.appActions.dataSlots[‘ds_documentKey’];
var docRef = db.collection(‘scores’).doc(documentKey)
docRef.get().then(function(doc)


#9

Thank you again Marcos,

I just tried the script being like this:

But when I try it I get the error:
07

What am I doing wrong?


#10

open the script editor in RS top bar and put this line to import firebase

import React, { Component } from ‘react’;
import ‘firebase/firestore’

save and close the editor. Compile


#11

I still had the error message but I could solve that issue by adding also:

import * as firebase from ‘firebase’

I modified a little bit more the script to get properly the document, but I believe the end of the script is not fine. I don’t know how to change it to make it work. Right now my script is:

var db = firebase.firestore();
var userID = this.props.appActions.dataSlots[‘ds_SlotUserId’];
var preloadID = this.props.appActions.dataSlots[‘ds_documentKey’];
var score = this.props.appActions.dataSlots[‘ds_score’];
var docRef = db.collection(‘scores’).where(‘preloadID’,’==’,preloadID).where(‘userID’,’==’,userID);

docRef.get().then(function(doc) {
if (docRef.exists) {
let setDoc = docRef.update({‘score’: score})
} else {
let setDoc = docRef.set({userID: userID}, {merge:true})
}
}).catch((fail) => {
});
return input;

As you can see, I have added another variable with the score value so it can be referred in the setDoc part of the function. However, it is not working. When I click in the button it just creates a new doc in the “scores” collection as it used to do.

I am guessing that I need to include the value of “setDoc” in the “input” value? Because as far as I know, when I say:

let setDoc = docRef.update({‘score’: score})

I have created the constant value “setDoc” but then I haven’t added it into input so it is part of the “input” variable?


#12

I have been thinking. Maybe there is another way to achieve it if I am not able to make it work. If there is some way to specify the name of the document when created, instead of leaving firestore to create it randomly, when the user creates another doc with the same name, all fields would be overwritten and I would be good with that because it would reflect the last obtained score.

So maybe that is a simpler approach?


#13

Hi Pedro. Hard to say from here. Always look the console for the expected results. First check the slots in application/storage. See if they are undefined. Next look the console it self for erros. You can learn a lot doing so


#14

You can do that, but consider that at some point you would like the other way around.


#15

I have been thinking about the issue and I believe that I definitely want the users scores in their own “folder”. Right now all scores are placed in a single collection named “scores” and I did that so I can query that data whenever I need: all scores ordered by date, or just scores where specific userID appears in a field. So far it’s been working just fine, but I starts to feel that it needs some order.

I have a different collection named users where the users store their favorites (a subcollection of documents). The documentKey for each one the users collection is their own userID so I can point to their data. Now I believe I am going to place each users scores in their own collection. That way it will be easier to locate their data and it would solved the issue with updating their scores.

Now I have a question about this new way to organize data. Will I be able to query all scores from all users? The path to that would be:

users/EVERYuserID/scores

Thank you.


#16

Check the Collection group queries: https://medium.com/@reactstudio/firebase-plugin-update-firestore-collection-group-queries-and-increment-925bf8603f1f


#17

Thank you so much! That is exactly what I would need. I am already working on moving the data from one location to another. But in order to put things working as they were, I have to make some changes in the project so the UI points to the right location. I was having some issues but I already figured it out.

Thank you!