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"
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? 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.
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.
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
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With