I want to use firebase query to search for a particular user by their name in my action-on-google app.I have used the following code but it dosen't prints anything.
const ref = firebase.database();
const nm = ref.child('Users');
const q =
nm.orderByChild('Name').equalTo('abcd');
q.on('value', snap => {
conv.ask(snap.val());
});
});
Can somebody help in rectifying my code.
Keep in mind that Javascript is pretty asynchronous, meaning that you need to make sure that your function understands the flow of your execution and more importantly know when it ends.
The standard way of doing this is through a Promise. Many async functions now return Promises.
So you can rewrite your code as:
app.intent('intent name', conv => {
const ref = firebase.database();
const nm = ref.child('Users');
const q = nm.orderByChild('Name').equalTo('abcd');
return q.once('value', snap => {
conv.ask(snap.val());
});
});
Related
I am trying to store the downloadLink from firebase's storage into firestore. I am able to set all the data, and I am able to set the link, the second time I click the "post" button.
I know the issue has to do with asynchronous functions, but I'm not experienced enough to know how to solve the issue.
In the "createPost" function, I am console logging "i am the URL: {url}" and in the "uploadFile" function, I am console logging "look at me {url}" to debug.
I noticed the "I am the URL" outputs nothing and then shortly after, the "look at me" outputs the URL.
setDoc() of course stores the imageLink as an empty string.
What can I do to solve this? Any help would be greatly appreciated or any documentation to help with my understanding of async functions.
Here is my code:
const PostModal = (props) => {
const makeid = (length) => {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const [descriptionText, setDescriptionText] = useState("");
const [addressText, setAddressText] = useState("");
const [venueText, setVenueText] = useState("");
const [startTimeText, setStartTimeText] = useState("");
const [endTimeText, setEndTimeText] = useState("");
const [shareImage, setShareImage] = useState("");
const [videoLink, setVideoLink] = useState("");
const [assetArea, setAssetArea] = useState("");
const [url, setURL] = useState("");
const { data } = useSession();
const storage = getStorage();
const storageRef = ref(storage, `images/${makeid(5) + shareImage.name}`);
const uploadFile = () => {
if (shareImage == null) return;
uploadBytes(storageRef, shareImage).then( (snapshot) => {
//console.log("Image uploaded")
getDownloadURL(snapshot.ref).then( (URL) =>
{
setURL(URL);
console.log(`look at me: ${URL}`)});
});
}
const createPost = async () => {
var idLength = makeid(25);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadFile()
console.log(`I am the URL: ${url} `)
setDoc(doc(db, "posts", idLength), {
eventDescription: descriptionText,
eventAddress: addressText,
venueName: venueText,
startTime: startTimeText,
endTime: endTimeText,
imageLink: url,
videoLink: videoLink,
username: data.user.name,
companyName: !data.user.company ? "" : data.user.company,
timestamp: Timestamp.now(),
});
}
const handleChange = (e) => {
const image = e.target.files[0];
if(image === '' || image === undefined) {
alert('not an image, the file is a ${typeof image}');
return;
}
setShareImage(image);
};
const switchAssetArea = (area) => {
setShareImage("");
setVideoLink("");
setAssetArea(area);
};
const reset = (e) => {
setDescriptionText("");
setAddressText("");
setVenueText("");
setStartTimeText("");
setEndTimeText("");
setShareImage("");
setVideoLink("");
setURL("");
props.handleClick(e);
};
This was taken from a reddit user who solved my answer. Big thank you to him for taking the time to write out a thoughtful response.
So, you're kinda right that your issue has a bit to do with asynchronicity, but it's actually got nothing to do with your functions being async, and everything to do with how useState works.
Suffice it to say, when you call uploadFile in the middle of your createPost function, on the next line the value of url has not yet changed. This would still be true even if uploadFile were synchronous, because when you call a useState setter function, in this case setURL, the getter value url doesn't change until the next time the component renders.
This actually makes perfect sense if you stop thinking about it as a React component for a moment, and imagine that this was just vanilla JavaScript:
someFunction () {
const url = 'https://www.website.com';
console.log(url);
anotherFunction();
yetAnotherFunction();
evenMoreFunction();
console.log(url);
}
In this example, would you ever expect the value of url to change? Probably not, since url is declared as const, which means if the code runs literally at all, it's physically impossible for the value of url to change within a single invocation of someFunction.
Functional components and hooks are the same; in a single "invocation" (render) of a functional component, url will have the same value at every point in your code, and it's not until the entire functional component re-renders that any calls to setURL would take effect.
This is an extremely common misunderstanding; you're not the first and you won't be the last. Usually, it's indicative of a design flaw in your data flow - why are you storing url in a useState to begin with? If you don't need it to persist across distinct, uncoupled events, it's probably better to treat it like a regular JavaScript value.
Since uploadBytes returns a promise, you could make uploadFile asynchronous as well, and ultimately make uploadFile return the information you need back to createPost, like this:
const uploadFile = async () => {
if (shareImage == null) return;
const snapshot = await uploadBytes(storageRef, shareImage);
// console.log("Image uploaded")
const URL = await getDownloadURL(snapshot.ref);
return URL;
};
All I've done here us un-nest your .then calls, pulling the trapped values out into the usable scope of your uploadFile function. Now, you can change that one line of createPost to this:
const url = await uploadFile();
and eliminate your useState altogether.
I am using Firestore realtime listeners in my Vue 3 / TypeScript app.
I created a composable that returns a ref to a user doc.
export const getUserListener = (userId: string) => {
const user = ref<User>();
const userDocRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(
userDocRef,
(snapshot) =>
(user.value = {
...snapshot.data(),
id: snapshot.id,
})
);
onUnmounted(unsubscribe);
return user;
};
It works great when populating the front end with user doc values.
But now I want to use those values in another composable, like so:
export const doSomethingWithUserListener = () => {
const user = useGetUserListener('user-id-abc-123');
if (user.value) {
console.log('User found');
} else {
console.log('User not found');
}
}
And this always returns User not found.
I already know why this is happening...
When getUserListener() first runs the user ref is undefined. So the ref returns as undefined immediately.
The onSnapshot part is asynchronous, so it executes some milliseconds later, and only then populates the ref with user data.
This is not an issue when populating fields on the front end, because users do not perceive the delay. But it causes an issue when using the listener in other composables, because the ref value is always undefined.
Since I known why this is happening I'm not looking for an explanation, but rather help with how to overcome this problem.
Summary: I want to use the listener ref value in other composables. But I can't because it always returns undefined.
Any ideas?
I am trying to make an "onUpdate" function that loads the document that has been updated. Then I want to load another document using the data received by the wildcards. So to summarize I want to access the document that was updated and one more that is in the same collection.
I want : /userProfiles/{doc1}/employees/{doc2} AND /userProfiles/{doc1}.
I can get them both but when I try to use the data from one, it doesn't read the previous data and gives me a ReferenceError.
The end goal is to use both these docs to send an email with nodemailer. Thanks for any help.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require('nodemailer');
admin.initializeApp();
exports.testLog = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
var info = [];
const doc1 = context.params.doc1;
const doc2 = context.params.doc2;
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
var email = doc.data().email;
var phone = doc.data().phone;
info.push(doc.data());
console.log(email, phone); // sees and gets info
return email, phone;
}),
db
.collection("userProfiles")
.doc(`${doc1}`)
.collection(`employees`)
.doc(`${doc2}`)
.get()
.then(doc => {
info.push(doc.data());
var Status = doc.data().Status;
console.log(phone, `${Status}`); //phone is undefined
if (`${Status}` === "Alarm") {
// replace with variables from the users settings page
console.log(`${info.phone}`); // phone is undefined
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: "xxxxxx#gmail.com",
pass: "xxxxxxxxxx"
}
});
// send mail with defined transport object
let mailOptions = {
from: '"Fred Foo 👻" <foo#example.com>',
to: `${info.phone}`, // tried phone as well
subject: "Hello ✔",
text: "216+?",
};
transporter.sendMail(mailOptions, error => {
if (error) {
return console.log(error);
} else {
return console.log("message sent");
}
});
}
console.log(Status);
// return
return console.log("im after the if statement. No alarm triggered");
})
.then(message => console.log(message.sid, "success"))
.catch(err => console.log(err))
);
});
So I want to get the phone number and the Status in these 2 images
The error that is returned:
ReferenceError: phone is not defined
There are two things that aren't quite working the way you expect leading to your problem:
The handling of promises isn't really passing data the way you expect -- in particular, the variables phone and email exist only in one promise handler, they aren't global in scope, so phone and email aren't being passed down the promise chain.
You don't actually need to ever read the second document, as the content is passed to you in the function itself. This actually greatly simplifies the overall thing you are doing, and makes dealing with the first point nearly trivial, since you can skip the second database call.
Look at this code where I have omitted the messaging code for clarity and just left in place most of the log messages:
exports.firestoreOnUpdateTest = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
// var info = []; I have removed this list, it is not necessary
const doc1 = context.params.doc1;
// no need to get the doc2 parameter, as we are handed the doc itself by the function call.
const doc2content = change.after.data();
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
const doc1content = doc.data();
const email = doc1content.email;
const phone = doc1content.phone;
console.log(email, phone); // sees and gets info
console.log(`No need to fetch doc2, as I already have it: ${JSON.stringify(doc2content)}`);
const Status = doc2content.Status;
console.log(`email for user is still: ${email}`); // email is now defined
console.log(phone, `${Status}`); // phone is now defined
if (`${Status}` === "Alarm") {
console.log(`${phone}`); // phone is now defined
return console.log('message would be sent here - code omitted')
}
console.log(Status);
return console.log("im after the if statement. No alarm triggered");
})
.catch(err => console.error(err))
);
});
In the new version, we just store the content from the document that triggered us, including the Status parameter. We then fetch the document with the content we need -- at the higher level in the tree. Once that document is returned, we just process it and combine with the data from doc2. All the fields are now defined (assuming, of course, the database objects are well-formed).
Your messaging code would be re-inserted right were the obvious log message is.
Finally, the info list I don't think is necessary now, so I've removed it. Instead, I recommend you build what you need as you construct the message itself from the data already on hand. That said, your original code wasn't accessing it correctly (that is, as a list) anyway and may have been confusing you further.
Finally, I haven't addressed the use of the Nodemailer module as the question focused primarily on the undefined fields, but I suspect your original code may not be entirely correct either -- as it doesn't either return a promise back from sendMail() or perform an await on that call (and make the entire function async), so you will need to look at that more closely.
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 simple function uploaded to Firebase Functions, that uses an external dependency (jimp). My code has some "console.log()" to trace what is happening when the function is triggered (by a 'write' action in Firstore).
According to the logs I can see in the firebase console,
every step of the function is running correctly, until it reaches the first call to the jimp object. At this point, no more logs on the console.
const Jimp = require('jimp');
const os = require('os');
...
exports.manageImage2 = functions.firestore
.document('/items/{docId}')
.onWrite(event => {
console.info('event triggered:', event);
console.log('tmpDir', os.tmpdir());
const originalUrl = data.original_file_url;
const imageSize = data.size;
if (imageSize > maxSize) {
console.log(`Image is too big (${imageSize})`);
Jimp.read(originalUrl, (err, image) => {
...
I guess there is something wrong with the dependency, but I have no clue what it can be.
I obviously ran my code locally with any issue and also made sure the library is correctly listed in the dependencies of package.json
Any idea?
Jimp.read is a callback styled asynchronous function. Use promise for adapt it to functions.
exports.manageImage2 = functions.firestore
.document('/items/{docId}')
.onWrite(event => {
console.info('event triggered:', event);
console.log('tmpDir', os.tmpdir());
const originalUrl = data.original_file_url;
const imageSize = data.size;
if (imageSize > maxSize) {
console.log(`Image is too big (${imageSize})`);
return new Promise(resolve => Jimp.read(originalUrl, (err, image) => {
// …
// resolve(result);
}));
}
});
For explanation read this: https://firebase.google.com/docs/functions/terminate-functions.