Firebase functions issue and the difference between .add() vs. .doc().set()

Viewed 276

I face the following issue since a few weeks (hasn't been an issue before):

1 .add(data)

const saveNewDoc = functions.https.onCall((data: NewDocWrite, context: CallableContext) => {
  return adminDb
    .collection(data.collectionPath)
    .add(data.data)
})

Document gets successfully created but there's still an error "INTERNAL" returned to the client and also listed in the functions log:

Unhandled error RangeError: Maximum call stack size exceeded
    at baseIteratee (/srv/node_modules/lodash/lodash.js:3464:26)
    at getIteratee (/srv/node_modules/lodash/lodash.js:5932:33)
    at Function.map (/srv/node_modules/lodash/lodash.js:9556:31)
    at encode (/srv/node_modules/firebase-functions/lib/providers/https.js:236:18)
    at /srv/node_modules/lodash/lodash.js:13402:38
    at /srv/node_modules/lodash/lodash.js:4911:15
    at baseForOwn (/srv/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/srv/node_modules/lodash/lodash.js:13401:7)
    at encode (/srv/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /srv/node_modules/lodash/lodash.js:13402:38

2 .doc().set(data)

Replacing add() by doc().set() creates the document without any errors.

Any idea why this happens? The "workaround" is simple just using doc().set() but I wonder why there even is the add() then. Can't we just use doc().set() for any new document?

2 Answers

with set you are selecting a document (you assign your own id) and add you are letting firestore to assign an id for you.

A small (not that small) detail here, your callable functio should return a promise that resolves with the response to send to the client, your function returns a promise that is resolved after an operation against the database.

The line your code is complaining about looks like this:

if (_.isArray(data)) {
  return _.map(data, encode);
}

I would put more attention in the data you are sending more than the method you are using.

const saveNewDoc = functions.https.onCall(async (data: NewDocWrite, context: CallableContext) => {
  await adminDb.collection(data.collectionPath).add(data.data)
  // OR
  await adminDb.collection(data.collectionPath).doc().set(data.data)
  return Promise.resolve({ ok: true })

})

Actually doc().set() is equivalent to .add(), I normally use set cause I like to have control over the docs Ids I'm setting, but leaving it empty is ok as well. Try that, avoiding returning the promise against the database exec should be good.

The reason I've found is simple and clear cut:

add() returns Promise<FirebaseFirestore.DocumentReference> while set() returns `Promise

The operation of adding a new document works in either way but once done, the response is sent back to the client, either the document reference or the write result wrapped in a promise. As there is only valid JSON allowed in the response, returning a document reference will result in an error, because it is not in a valid JSON format.

So once the operation is done, either resolve an empty Promise or transform the result into valid JSON before wrapping it in the response - or use set().

Related