I would like to assign the value of a field from a document to a constant to use it across several functions.
const stripeAccountId = firestore.doc('orgs/' + subscription.orgId).get()
.then( org => {
return org.data().stripeAccountId
})
The firestore.doc('orgs/' + subscription.orgId).get().then(...) method returns a promise. More info: https://scotch.io/tutorials/javascript-promises-for-dummies
Promises are async and you need to assign the stripeAccountId inside the arrow function specified inside the then.
I don't know where you will use it, but the stripeAccountId will only be filled after the promise is resolved.
const stripeAccountId = null;
firestore.doc('orgs/' + subscription.orgId).get().then(org => {
stripeAccountId = org.data().stripeAccountId;
})
console.log(stripeAccountId); // null
const sufficientTimeInMillisToResolveThePromise = 10000;
setTimeout(() => {
console.log(stripeAccountId); // some-id
}, sufficientTimeInMillisToResolveThePromise);
Related
I am using firebase for my app and the data i read i want to put that in state to use it in different places.
it kinda works but when i want to console.log the state it updates like 30 times a second, am i doing something wrong?
this is my code
const db = firebase.firestore();
const [PBS1Detail, setPBS1Detail] = useState();
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
return setPBS1Detail(data);
}
console.log(PBS1Detail)
i already tried to store it in a variable instead of state but thats not working for me, i can't get the variable from the function global
i am a noob i get it xd
You don't need a return statement when setting state. Also, it looks like you're performing some async function which means that when your component renders for the first time, PBS1Detail will be undefined since the db is a pending Promise.
You could/should put your async functions in useEffect hook:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
}, [])
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
setPBS1Detail(data);
}
Finally, your renderTracks function doesn't seem correct as it appears you're looping over docs and resetting your state each time.
Instead, maybe consider having an array for PBS1Detail
const [PBS1Detail, setPBS1Detail] = useState([]);
Then modify your async call:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push(renderTracks(doc))
}
)
setPBS1Detail(results)
});
}, [])
const renderTracks = (doc) => {
return doc.data().data[0].Module;
}
This way you're only setting state once and thus avoiding unnecessary re-renders and you're saving all of your docs instead of overwriting them.
I'm trying to nest an onSnapshot to handle initial loading of data from two different documents. Here's my full function:
useEffect(() => {
return db
.collection("accounts").doc(currentAccount)
.collection("farms").doc(currentFarm)
.collection("sensors").doc(currentSensor)
.onSnapshot((sensorSnapshot) => {
// Save sensor metadata
const sensorData = sensorSnapshot.data();
setSensorData(sensorData);
console.log(sensorData);
const sensorReadingsId = sensorData.sensorReadingsId.toString();
// Problem starts
db.collection("readings").doc(sensorReadingsId).onSnapshot((readingsSnapshot) => {
const list = [];
readingsSnapshot.forEach((reading) => {
const { name, value1, value2, value3, voltage } = reading.data();
list.push({
id: reading.id,
name,
value1,
value2,
value3,
voltage
});
});
setReadingsData(list);
console.log(readingsData);
});
// Finally, finish
setLoading(false);
});
}, []);
Which gives me an error: undefined is not a function (near '...readingsSnapshot.forEach...')
If I comment out the db.collection("readings")... part, it operates as expected. I just can't work out what I'm doing wrong with the second onSnapshot. How do I fix this?
readingsSnapshot is a DocumentSnapshot type object, representing a single snapshot of the document referenced by db.collection("readings").doc(sensorReadingsId). As you can see from the linked API documentation, it doesn't have a forEach method, as there is nothing to iterate like there is with QuerySnapshot.
If you want to work with the fields of the document of that snapshot, just call data() on it directly.
db.collection("readings").doc(sensorReadingsId).onSnapshot((readingsSnapshot) => {
const { name, value1, value2, value3, voltage } = readingsSnapshot.data();
// do what you want from here
});
I'm checking the onUpdate of a {postId}, and I want to run a firebase database call with that same {postId}.. if that makes sense. Here is my code:
exports.handleVoteKarma = functions.database
.ref('upvotes/{postId}')
.onUpdate(async change => {
const scoreBefore = change.before.val() || 0;
const scoreAfter = change.after.val();
//This {postId} should be the same as the one above for the upvotes/{postId}
adb.ref('{item}/{loc}/{postId}/score').once('value').then((usr) => {
});
return null;
});
Essentially I want the {postId} in upvotes/ to have the same value as the {postId} when I check the score.. will it work like this?
Realtime Database triggers accept a second argument, which you're not using in your function:
exports.handleVoteKarma = functions.database
.ref('upvotes/{postId}')
.onUpdate(async (change, context) => {
// note the "context" parameter here
});
This is an EventContext object and it contains a params property with the values of the wildcards in the path. You'd use it simply like this:
const postId = context.params.postId
You can then use the postId string later to build other refs.
There is more discussion in the documentation.
I have a Firebase Cloud Function that assigns a number to a user on onWrite. The following code works but something is wrong because the console logs state Function returned undefined, expected Promise or value.
I'm also not sure how to refer to the root from inside the onWrite so I've created several "parent" entries that refer to each other. I'm sure there is a better way.
onWrite triggers on this:
/users/{uid}/username
The trigger counts the children in /usernumbers and then writes an entry here with the uid and the child count + 1:
/usernumbers/uoNEKjUDikJlkpLm6n0IPm7x8Zf1 : 5
Cloud Function:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
parent3usernumbers.once("value")
.then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
return console.log('User Number Written', uid, a);
});
});
});
Is there a better way to do this? How can I get the Function Returned Undefined error to go away?
I should also mention it takes a few seconds for the 'usernumber' entry to be written. I'm guessing it's waiting for the function to return something.
Your function have to return a Promise :
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
return new Promise((resolve, reject) => {
parent3usernumbers.once("value").then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
console.log('User Number Written', uid, a);
resolve({uid : uid, a : a})
}).catch(function(e) {
reject(e)
})
});
});
});
I have observed this behavior occasionally with both onCreate and onDelete triggers.
Both the executions happened for the same document created in firestore. There's only one document there so I don't understand how it could trigger the handler twice. the handler itself is very simple:
module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
const db = admin.firestore();
const params = event.params;
const data = event.data.data();
// empty
});
this doesn't happen all the time. What am I missing?
See the Cloud Firestore Triggers Limitations and Guarantees:
Delivery of function invocations is not currently guaranteed. As the
Cloud Firestore and Cloud Functions integration improves, we plan to
guarantee "at least once" delivery. However, this may not always be
the case during beta. This may also result in multiple invocations
for a single event, so for the highest quality functions ensure that
the functions are written to be idempotent.
There is a Firecast video with tips for implementing idempotence.
Also two Google Blog posts: the first, the second.
Based on #saranpol's answer we use the below for now. We have yet to check if we actually get any duplicate event ids though.
const alreadyTriggered = eventId => {
// Firestore doesn't support forward slash in ids and the eventId often has it
const validEventId = eventId.replace('/', '')
const firestore = firebase.firestore()
return firestore.runTransaction(async transaction => {
const ref = firestore.doc(`eventIds/${validEventId}`)
const doc = await transaction.get(ref)
if (doc.exists) {
console.error(`Already triggered function for event: ${validEventId}`)
return true
} else {
transaction.set(ref, {})
return false
}
})
}
// Usage
if (await alreadyTriggered(context.eventId)) {
return
}
In my case I try to use eventId and transaction to prevent onCreate sometimes triggered twice
(you may need to save eventId in list and check if it exist if your function actually triggered often)
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {
const prize = 1000
const eventId = context.eventId
if (!eventId) {
return false
}
// increment money
const p1 = () => {
const ref = db.doc('...')
return db.runTransaction(t => {
return t.get(ref).then(doc => {
let money_total = 0
if (doc.exists) {
const eventIdLast = doc.data().event_id_last
if (eventIdLast === eventId) {
throw 'duplicated event'
}
const m0 = doc.data().money_total
if(m0 !== undefined) {
money_total = m0 + prize
}
} else {
money_total = prize
}
return t.set(ref, {
money_total: money_total,
event_id_last: eventId
}, {merge: true})
})
})
}
// will execute p2 p3 p4 if p1 success
const p2 = () => {
...
}
const p3 = () => {
...
}
const p4 = () => {
...
}
return p1().then(() => {
return Promise.all([p2(), p3(), p4()])
}).catch((error) => {
console.log(error)
})
})
Late to the party, I had this issue but having a min instance solved the issue for me
Upon looking #xaxsis attached screenshot, my function took almost the amount of time about 15 seconds for the first request and about 1/4 of that for the second request