Firebase firestore cloud functions showing Error: Invalid use of type "undefined" as a Firestore argument

16,787

Solution 1

The error message is this:

Invalid use of type "undefined" as a Firestore argument.

You can see in your stack trace that this happens when you call set() with an object on a DocumentReference. It turns out that one of the values you're passing in the object is undefined. Check each of the values that you're passing and make sure all of them have an actual value:

 .set({
   id : id,
   branchName : branchName,
   currencyName : currencyName,
   sellingRate : sellingRate,
   buyingRate :buyingRate
  });

It's impossible to tell which one it is from the error message, so you'll have to print them all out do something to check each and every one of them.

Solution 2

When you .set an object but one of the fields in the object is undefined you will get this error.

The problem is that when you use console.log to display the object it does not show the undefined variables so it is difficult to trace.

Use the following instead of console.log to find the element that is causing the problem.

const util = require('util');
console.log(util.inspect(myObject, {showHidden: false, depth: null}));

This will give you an output as follows:

{ origin: 'AMS',
  destination: undefined,
  duration: 94,
  carrier: 'KL',
  flight_number: '2977',
  departure: '2019-06-11T15:34:00',
  arrival: '2019-06-11T17:08:00',
  type: 'flight' }

Solution 3

Understanding the issue:

The following error message is relatively clear

Invalid use of type "undefined" as a Firestore argument.

This means, that in your set or update reference method, you've passed an argument, which had value of undefined, which is by definition not a valid Firestore argument.

Fixing the issue:

Personally I find everything much easier to understand with an example.

Let's say my function editStafferPoolPermissions errors with the "Invalid use of type "undefined" as a Firestore argument" error message. Which is defined the following way:

export const editStafferPoolPermissions(data: {
 businessId: string,
 stafferId: string,
 poolPermissions: string[],
 areCustomPermissions: boolean,
 wage: number,
}) => //...

To find out which argument (or even arguments) it is exactly

  1. Open your Firebase developer console and open the "Functions" tab.
  2. Select the logs tab
  3. Filter out the exact name of the function and check the arguments passed

This allow us to see, which arguments were passed and which weren't.

enter image description here

As you can see, the wage parameter is missing in the oncall invocation of my https cloud function, causing the error to crash. This means I either forgot to pass or am passing the wage parameter incorrectly.

Obviously the undefined argument will wary depending on how your function is defined but hopefully this should be enough for you to get the gist of how to trace and fix the issue. It usually boils down to two options, you either forgot to pass it altogether or are passing it incorrectly from the front-end (or the data is incorrectly structured)

What if I want to allow undefined (optional) arguments?

What most of the answers on internet don't tackle, is the scenario where we might actually leave an argument undefined on purpose.

I actually had a lot of trouble finding this for the longest time, where I had to resort to writing a very shoddy looking cloud funciton full of nested ifs, when I wanted to create one, that would also allow optional parameters as undefined and simply ignore them if they aren't passed.

To continue from our previous example, let's say we changed the wage argument to optional, i.e.

wage?: number
// ... other params

So now, if we call the editStafferPoolPermissions cloud functions, it shouldn't matter whether the wage is passed or not.

Luckily, as of May 29 2020, there has been added a new argument to the SetOptions called ignoreUndefinedProperties, which allows you to simply ignore undefined parameters.

For example, the inside of my editStafferPoolPermissions could look something like this.

await firestore.collection('staffers').doc(stafferId).set({
  poolPermissions,
  areCustomPositions,
  wage,
}, { ignoreUndefinedProperties: true })

Troubleshooting legacy firebase versions

Given this newly added argument is relatively recent and even in my work I was on relatively older codebase which for legacy reasons could not have the most up-to-date firebase version, so for this reason, I needed to create a polyfill, which would mimic the ignoreUndefinedProperties argument.

I've created the following function:

export const ignoreUndefinedSet = async (
  // depending on your namespace
  // & /types version, you can use firebase.firestore.<name> instead
  reference: FirebaseFirestore.DocumentReference,
  data: FirebaseFirestore.DocumentData,
  options?: FirebaseFirestore.SetOptions,
  checkNestedObjects = true,
) => {
  const isPlainObject = (val: unknown) =>
     typeof val === 'object' && val !== null &&
     !(val instanceof Date) && !Array.isArray(val)
  const keepDefinedProperties = (
     obj: FirebaseFirestore.DocumentData,
     nestedCheck = true,
  ) =>
   Object.entries(data).reduce(
      (result, [key, value]) => (
           value === undefined
              ? result
              : (nestedCheck && isPlainObject(value))
                 ? Object.assign(result, { [key]: keepDefinedProperties(value) })
                 : Object.assign(result, { [key]: value })
      ),
      {}
  )
  const onlyDefinedProperties = keepDefinedProperties(data, checkNestedObjects)
  await reference.set(onlyDefinedProperties, { ...options })
}

So with my polyfill, you can use ignore the undefined properties even in older firebase versions. In fact it actually might be useful even on newer ones, because it allows you to decide if you want to ignore the undefined properties only at the object root level, or also at potentially nested object properties.

So essentially these two statements are equivalent

await reference.set(data, { ignoreUndefinedProperties: true })
// newer firebase version
await ignoreUndefinedSet(reference, data) // my polyfill

Note, you can also pass other SetOptions or disable the nested objects check

await ignoreUndefinedSet(reference, data, { merge: true }, false)
// does ignoreUndefinedProperties only at root level and uses the merge method
Share:
16,787
pepe
Author by

pepe

Updated on June 12, 2022

Comments

  • pepe
    pepe almost 2 years

    I have a project to add currency details into the firestore database and my project is doing with ionic 3

    Whenever I add a new document to the collection a trigger function onCreate() will execute and update the document named 'updated'.

    But the trigger function always showing an error.

    Error: Invalid use of type "undefined" as a Firestore argument.
        at Object.exports.customObjectError.val [as customObjectError] (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/validate.js:164:14)
        at Function.encodeValue (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/document.js:808:20)
        at Function.encodeFields (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/document.js:678:36)
        at Function.fromObject (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/document.js:218:55)
        at WriteBatch.set (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/write-batch.js:291:39)
        at DocumentReference.set (/user_code/node_modules/firebase-admin/node_modules/@google-cloud/firestore/src/reference.js:419:8)
        at Object.<anonymous> (/user_code/lib/index.js:28:10)
        at next (native)
        at /user_code/lib/index.js:7:71
        at __awaiter (/user_code/lib/index.js:3:12)
    

    sombody please help..

    i have spent lot of time on it.

    Here is the code :

    import * as functions from 'firebase-functions';
    
    const admin = require('firebase-admin');
    admin.initializeApp();
    
    exports.createCurrency = functions.firestore
    .document('Exchange/{ExchangeId}')
    .onCreate( async (snap, context) => {
    
    const id: string = snap.data().id;
    const branchName: string = snap.data().branchName;
    const currencyName: string = snap.data().currencyName;
    const buyingRate : string = snap.data().buyingRate;
    const sellingRate : string = snap.data().sellingRate;
    
    
     const newUser= admin.
     firestore()
     .doc(`Exchange/updated`)
     .set({
       id : id,
       branchName : branchName,
       currencyName : currencyName,
       sellingRate : sellingRate,
       buyingRate :buyingRate
      });
    
     return newUser;
    
    
     });