How to update an "array of objects" with Firestore?

174,205

Solution 1

Edit 08/13/2018: There is now support for native array operations in Cloud Firestore. See Doug's answer below.


There is currently no way to update a single array element (or add/remove a single element) in Cloud Firestore.

This code here:

firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
  { sharedWith: [{ who: "[email protected]", when: new Date() }] },
  { merge: true }
)

This says to set the document at proprietary/docID such that sharedWith = [{ who: "[email protected]", when: new Date() } but to not affect any existing document properties. It's very similar to the update() call you provided however the set() call with create the document if it does not exist while the update() call will fail.

So you have two options to achieve what you want.

Option 1 - Set the whole array

Call set() with the entire contents of the array, which will require reading the current data from the DB first. If you're concerned about concurrent updates you can do all of this in a transaction.

Option 2 - Use a subcollection

You could make sharedWith a subcollection of the main document. Then adding a single item would look like this:

firebase.firestore()
  .collection('proprietary')
  .doc(docID)
  .collection('sharedWith')
  .add({ who: "[email protected]", when: new Date() })

Of course this comes with new limitations. You would not be able to query documents based on who they are shared with, nor would you be able to get the doc and all of the sharedWith data in a single operation.

Solution 2

Firestore now has two functions that allow you to update an array without re-writing the entire thing.

Link: https://firebase.google.com/docs/firestore/manage-data/add-data, specifically https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Update elements in an array

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. arrayRemove() removes all instances of each given element.

Solution 3

Here is the latest example from the Firestore documentation:

firebase.firestore.FieldValue.ArrayUnion

var washingtonRef = db.collection("cities").doc("DC");

// Atomically add a new region to the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});

// Atomically remove a region from the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});

Solution 4

You can use a transaction (https://firebase.google.com/docs/firestore/manage-data/transactions) to get the array, push onto it and then update the document:

    const booking = { some: "data" };
    const userRef = this.db.collection("users").doc(userId);

    this.db.runTransaction(transaction => {
        // This code may get re-run multiple times if there are conflicts.
        return transaction.get(userRef).then(doc => {
            if (!doc.data().bookings) {
                transaction.set({
                    bookings: [booking]
                });
            } else {
                const bookings = doc.data().bookings;
                bookings.push(booking);
                transaction.update(userRef, { bookings: bookings });
            }
        });
    }).then(function () {
        console.log("Transaction successfully committed!");
    }).catch(function (error) {
        console.log("Transaction failed: ", error);
    });

Solution 5

Sorry Late to party but Firestore solved it way back in aug 2018 so If you still looking for that here it is all issues solved with regards to arrays.

https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.htmlOfficial blog post

array-contains, arrayRemove, arrayUnion for checking, removing and updating arrays. Hope it helps.

Share:
174,205

Related videos on Youtube

charnould
Author by

charnould

Updated on January 10, 2022

Comments

  • charnould
    charnould over 2 years

    I'm currently trying Firestore, and I'm stuck at something very simple: "updating an array (aka a subdocument)".

    My DB structure is super simple. For example:

    proprietary: "John Doe",
    sharedWith:
      [
        {who: "[email protected]", when:timestamp},
        {who: "[email protected]", when:timestamp},
      ],
    

    I'm trying (without success) to push new records into shareWith array of objects.

    I've tried:

    // With SET
    firebase.firestore()
    .collection('proprietary')
    .doc(docID)
    .set(
      { sharedWith: [{ who: "[email protected]", when: new Date() }] },
      { merge: true }
    )
    
    // With UPDATE
    firebase.firestore()
    .collection('proprietary')
    .doc(docID)
    .update({ sharedWith: [{ who: "[email protected]", when: new Date() }] })
    

    None works. These queries overwrite my array.

    The answer might be simple, but I could'nt find it...

  • ItJustWerks
    ItJustWerks over 6 years
    This is so frustrating...but thanks for letting me know I'm not going crazy.
  • Sajith Mantharath
    Sajith Mantharath over 6 years
    this is big drawback, Google has to fix it asap.
  • quicklikerabbit
    quicklikerabbit almost 6 years
    @DougGalante's answer indicates this has been fixed. Use the arrayUnion method.
  • Artur Carvalho
    Artur Carvalho almost 6 years
    Is there any way to update a specific index from the array?
  • kernelman
    kernelman almost 6 years
    How to use this array update feature with "react-native-firebase" ? (I cant find this on official docs of react-native-firebase)
  • Adam
    Adam over 5 years
    @ArturCarvalho No, the reason why is explained in this video youtube.com/…
  • Yogendra Patel
    Yogendra Patel about 5 years
    @ArturCarvalho have you find any solution for update a specific index from the array?
  • Ilir Hushi
    Ilir Hushi almost 5 years
    At if statement you should change it into this, because you are missing the documentReference add userRef as shown: transaction.set(userRef, { bookings: [booking] });
  • michelepatrassi
    michelepatrassi over 4 years
    for who needs to do it on the client, use "import * as firebase from 'firebase/app';" then "firebase.firestore.FieldValue.arrayUnion(NEW_ELEMENT)"
  • Rahul Ramesh
    Rahul Ramesh about 4 years
    Beautiful, this should be the top answer!
  • Veeresh Devireddy
    Veeresh Devireddy about 4 years
    @nifCody, this will indeed add a new string element "greater_virginia" to an existing array "regions". I have tested it successfully, and definitely not adding "object". It is in sync with the question as stated: "push new records".
  • MadMac
    MadMac almost 4 years
    Be great if there was a way to update an item in an array with a specific id. Like an arrayUnion but with a merge: true. At the moment it requires 2 operations to remove the array item and then add it again with the new data.
  • Jenea Vranceanu
    Jenea Vranceanu almost 4 years
    Please, explain your answer.
  • jasie
    jasie almost 4 years
  • Hoppeduppeanut
    Hoppeduppeanut over 3 years
    While this might answer the question, if possible you should edit your answer to include a short explanation of how this code block answers the question. This helps to provide context and makes your answer much more useful to future readers.
  • Benyam
    Benyam over 3 years
    @Hoppeduppeanut yes you are right. I definitely accept the criticism for not adding an explanation for my answer. I have edited my answer. hope it helps much better now.
  • Ayush Kumar
    Ayush Kumar almost 3 years
    This will re write the whole array