1

Today I noticed a bug in my code where certain cleanup actions were not performed on duplicated data in my Firestore.

When writing this code a while back I was already aware of the finicky nature of using 'array-contains' with objects. I know they need to have the exact same shape. Firebase also inserts all the objects (Maps) with the keys in alphabetical order, so I even created a helper function that makes sure the objects have the keys in that same order as well.

export type UserReference = {
  name?: string;
  uid: AppUser['uid'];
};

export const getUserRef = (user: AppUser | DbUser): UserReference => ({
  name: user.name,
  uid: user.uid,
});

Now in my code, it still doesn't work...

 const linkedGroupQuery = getFirestore()
    .collection(constants.dbCollections.groups)
    .where('owners', 'array-contains', getUserRef(user));

...

 const querySnapshot = await linkedGroupQuery.get();

 querySnapshot.forEach((doc) => {
  const linkedGroup = doc.data() as DbGroup;
  const owners = linkedGroup.owners.filter((o) => o.uid !== uid);
  batch.update(doc.ref, { owners });
 });

I can see in my Firebase dashboard that the records get inserted as expected, yet this cleanup code does not work.

Meanwhile I have this very similar code that does work:

    const groupRef: GroupReference = getGroupRef(group);

    const batch = getFirestore().batch();

    const usersWithGroup = await getFirestore()
      .collection(constants.dbCollections.users)
      .where('groups', 'array-contains', groupRef)
      .get();

    // Remove group from all users
    usersWithGroup.forEach((userSnapshot) => {
      const userData = userSnapshot.data() as DbUser;
      const userGroups = userData.groups?.filter((g) => g.id !== group.id);
      batch.update(userSnapshot.ref, { groups: userGroups });
    });

What could be going on here?

Edit:
It turned out to be a problem with async/await and batch writes, not with 'array-contains'

Jonathan
  • 8,771
  • 4
  • 41
  • 78

1 Answers1

0

Today, I learned the importance of using await when you want your asynchronous functions to execute in the midst of another asynchronous function, prior to other lines of code.

const batch = getFirestore().batch();

...

// Await was missing here in this async helper
await severGroupOwnerLinksWithUser(user.uid, batch);

...

batch.commit();

I had written several helper functions that optionally accepted a Firestore WriteBatch. However, because these functions were asynchronous and lacked an await keyword, they ran after the batch was committed. This resulted in some bewildering bugs. Some parts of those functions did work due to the parallel nature of these asynchronous operations, while others did not, as they depended on the batch that had already been committed.

Jonathan
  • 8,771
  • 4
  • 41
  • 78