Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update individual map in cloud firestore document

Final Update I changed from using a transaction based on the answer below from andresmijares, to using set().

This now allows me to write the data to the DB.

var gradeDocRef = db.collection("students").doc(studentId);
                            console.log(gradeDocRef);

                            var setWithMerge = gradeDocRef.set({
                                "UnitGrades": {
                                            [unitNo]: {
                                                "CG": CG,
                                                "PG": PG, 
                                                "TG": TG
                                                }
                                            }
                                }, { merge: true });

Edit I altered code for the transaction based on the comment from andresmijares below.

transaction.set(gradeDocRef, {merge: true}, {

but then get this error?

Unknown option 'UnitGrades' passed to function Transaction.set(). Available options: merge, mergeFields


I have a cloud firestore database that contains a collection students. Each student collections contains a student document with a map and submap like below

UnitGrades: 
    {
     IT1:
       {                                  
        CG: "F"
        PG: "F"                                          
        TG: "F"
        id: "IT1"
        name: "Fundamentals of IT"
        type: "Exam"
    }

I have 10 units within the map UnitGrades Each student has the same combination of units

I want to update the map based upon an HTML form in bootstrap (the form is working, plus is quite long so not put it in here)

i.e. change the student grades

I have used the firestore transaction update documentation and adapted slightly to take in data from a form in HTML.

let studentId = $(this).attr("data-student-id");
let unitNo = $(this).attr("data-unit");
let CG = $(this).attr("data-CG");
let PG = $(this).attr("data-PG");
let TG = $(this).attr("data-TG");

// Create a reference to the student doc.
var gradeDocRef = db.collection("students").doc(studentId);
console.log(gradeDocRef);
    return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
    return transaction.get(gradeDocRef).then(function(gradeDoc) {

   if (!gradeDoc.exists) {
     throw "Document does not exist!";
}

// update the grades using a transaction

   transaction.update(gradeDocRef, {

// in here is my error, I need to be able to select the map
// for the variable for UnitNo only and not wipe the other maps

    "UnitGrades": {

    [unitNo]: {

    "CG": CG,

    "PG": PG, 

    "TG": TG                                                }
});
});

}).then(function() {

console.log("Transaction successfully committed!");

}).catch(function(error) {

console.log("Transaction failed: ", error);
console.log(studentId);

});

The code I have implemented updates the correct unit map but then wipes the rest of the UnitGrades. What I really want is to update the unit map identified in the variable

UnitNo and then leave the rest of the units untouched.

e.g. Currently if I update IT1 this correctly updates the grades within the map but then wipes units IT2, IT3, IT12 etc. from the UnitGrades map. I really want IT2, IT3, IT12 etc. to remain in place untouched and IT1 to be updated with the new values. e.g "F" changes to "P"

like image 433
user1693026 Avatar asked Jun 08 '19 13:06

user1693026


People also ask

How do I update firestore single field?

There are two methods you can use to update an existing document in Firebase Version 9 Cloud Firestore. Using updateDoc(), you can only update the document field of an existing document in the Firestore Database which can be: Adding a new document field. Updating a value of an existing document field.

Is there any way to update a specific index from the array in firestore?

Is there any way to update a specific index from the array in Firestore? No, there is not! This is not possible because if you want to perform an update, you need to know the index of that particular element. When talking about Cloud Firestore arrays, the things are different that you might think.

How do you update data in firestore array?

When it comes to the official documentation regarding how to update elements in an array in Firestore, it says that: If your document contains an array field, you can use arrayUnion() and arrayRemove() to add and remove elements. arrayUnion() adds elements to an array but only elements not already present.


2 Answers

change these lines:

transaction.update(gradeDocRef, {
    "UnitGrades": {
    [unitNo]: {
       "CG": CG,
       "PG": PG, 
       "TG": TG                                                }
});

for this

transaction.set(gradeDocRef, {
    `UnitGrades.${unitNo}`: {
       "CG": CG,
       "PG": PG, 
       "TG": TG 
}, { merge: true });

It works like this as far as I know:

assuming you doc looks like this:

 {
   "fantasticsFours": {
     "thing": { ... },
     "susan": { ... },
     "mister": { ... }
   }
 }

we need to add {"humanTorch" :{...}}

With set + merge

db.collection('heroes').doc(`xxxXXXxxx`).set({
  "fantasticsFours": {
    "humanTorch":{ ... }
  }
}, {merge:true})

will result in this data:

 {
   "fantasticsFours": {
     "thing": { ... },
     "susan": { ... },
     "mister": { ... },
     "humanTorch":{ ... }
   }
 }

with Update

db.collection('heroes').doc(`xxxXXXxxx`).update({
  "fantasticsFours": {
    "humanTorch":{ ... }
  }
})

will result in this data:

 {
   "fantasticsFours": {
     "humanTorch":{ ... }
   }
 }

More here

like image 51
andresmijares Avatar answered Sep 17 '22 14:09

andresmijares


The following should do the trick:

  //....
  return db.runTransaction(function(transaction) {
    // This code may get re-run multiple times if there are conflicts.
    return transaction
      .get(gradeDocRef)
      .then(function(gradeDoc) {
        if (!gradeDoc.exists) {
          throw 'Document does not exist!';
        }

        // update the grades using a transaction


        transaction.update(
          gradeDocRef,
          'UnitGrades.' + unitNo,
          {
            CG: CG,

            PG: PG,

            TG: TG
          }
          // in here is my error, I need to be able to select the map
          // for the variable for UnitNo only and not wipe the other maps
        );
      })
      .then(function() {
        console.log('Transaction successfully committed!');
      })
      .catch(function(error) {
        console.log('Transaction failed: ', error);
        console.log(studentId);
      });

By doing

transaction.update(gradeDocRef, {
    "UnitGrades": { ... }
});

you are replacing the entire UnitGrades field by a new map, therefore you erase the existing map and submaps values.

What you need to do is to only replace a specific "submap". For that you need to use the dot notation, as explained in the documentation for the update() method: "Fields can contain dots to reference nested fields within the document."

Note that there are two different ways to call the update() method:

update(documentRef: DocumentReference, data: UpdateData): Transaction

or

update(documentRef: DocumentReference, field: string | FieldPath, value: any, ...moreFieldsAndValues: any[]): Transaction

In this case we use the second way and we define the path of the nested "submap" with 'UnitGrades.' + unitNo (dot notation).


HTML Tester page

If you want to test the proposed solution, just save locally the following code as an HTML file and open it in a browser after you have a/Adapted the Firebase config and b/ created a Firestore document with id 1 under the students collection. Then change the value of unitNo, refresh the page in the browser and you will see the updates in the DB.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>

    <script src="https://www.gstatic.com/firebasejs/6.1.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/6.1.1/firebase-firestore.js"></script>
  </head>

  <body>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: 'xxxxxx',
        authDomain: 'xxxxxx',
        databaseURL: 'xxxxxx',
        projectId: 'xxxxxx',
        appId: 'xxxxxx'
      };

      firebase.initializeApp(config);

      var db = firebase.firestore();

      let studentId = '1';
      let unitNo = 'IT1';
      let CG = 'F';
      let PG = 'F';
      let TG = 'F';

      // Create a reference to the student doc.
      var gradeDocRef = db.collection('students').doc(studentId);
      console.log(gradeDocRef);
      db.runTransaction(function(transaction) {
        // This code may get re-run multiple times if there are conflicts.
        return transaction
          .get(gradeDocRef)
          .then(function(gradeDoc) {
            if (!gradeDoc.exists) {
              throw 'Document does not exist!';
            }

            transaction.update(
              gradeDocRef,
              'UnitGrades.' + unitNo,
              {
                CG: CG,

                PG: PG,

                TG: TG
              }

            );
          })
          .then(function() {
            console.log('Transaction successfully committed!');
          })
          .catch(function(error) {
            console.log('Transaction failed: ', error);
            console.log(studentId);
          });
      });
    </script>
  </body>
</html>
like image 22
Renaud Tarnec Avatar answered Sep 18 '22 14:09

Renaud Tarnec