Get data from Firebase in Vue.js component - firebase

I'm starting my first serious app with Vue.js and I have an issue gathering data from Firabase. The idea here is simply to get data linked to an user ID. My first though was to store that in a computed value, like so
export default {
...
computed: {
userInfo: function() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
return doc.data();
}
});
}
}
}
But, when I try to access this variable, it's undifined.
My guess is that the value is computed before the asynchronous call has ended. But I can't see how to get around it.

Indeed you have to take into account the asynchronous character of the get() method. One classical way is to query the database in the created hook, as follows:
export default {
data() {
return {
userInfo: null,
};
},
....
created() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
this.userInfo = doc.data();
}
});
}
}

Related

Why is the asnyc/await function not been waited?

I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.

How to fix undefined` cannot be serialized as JSON. Please use `null` or omit this value using getStaticprops

i am trying to fetch some data from an api using getStaticProps with next js. It returns the error undefinedcannot be serialized as JSON. Please usenull` or omit this value.
I have amended the code based on the solutions proposed online about the topic but none of them work.
export async function getStaticProps() {
const propertyForSale = await fetchApi(`${baseUrl}/properties/list?locationExternalIDs=5002&purpose=for-sale&hitsPerPage=6`);
const propertyForRent = await fetchApi(`${baseUrl}/properties/list?locationExternalIDs=5002&purpose=for-rent&hitsPerPage=6`);
return {
props: {
// Initial code
propertyForSale: propertyForSale?.hits,
propertyForRent: propertyForRent?.hits,
// the below snippet fixes the issue but return null
// propertyForSale: propertyForSale?.hits ?? null,
//propertyForRent: propertyForRent?.hits ?? null,
//the below snippet fixes the issue but return Unexpected token u in JSON at position 0
// propertyForSale: JSON.stringify(JSON.parse(propertyForSale?.hits))
//propertyForRent: JSON.stringify(JSON.parse(propertyForRent?.hits)),
}
}
}
fetchapi.js
export const baseUrl = 'https://bayut.p.rapidapi.com'
export const fetchApi = async (url) => {
const {result} = await axios.get((url), {
headers: {
'x-rapidapi-host': 'bayut.p.rapidapi.com',
'x-rapidapi-key': process.env.NEXT_PUBLIC_BAYU_API
},
});
console.log(result);
return result;
};
As Lazar pointed out in his comment, in your question's code snippet, you're trying to destructure a property that doesn't exist. And since axios returns an object with the data property, you're only left with destructuring the correct property:
const { data } = await axios.get(.....)
or...
const result = await axios.get(.....);
return result.data;
if you insist on the result thing :D
To fix the error I renamed constant result to data as per below.
I am not sure of what is the reason of this bug, if someone wanna add something to explain the reason why naming the constant data fixed the bug be my guess.
export const fetchApi = async (url) => {
const {data} = await axios.get((url), {
headers: {
'x-rapidapi-host': 'bayut.p.rapidapi.com',
'x-rapidapi-key': process.env.NEXT_PUBLIC_BAYU_API
},
});
return data;
};
export async function getStaticProps() {
const propertyForSale = await fetchApi(`${baseUrl}/properties/list?locationExternalIDs=5002&purpose=for-sale&hitsPerPage=6`);
const propertyForRent = await fetchApi(`${baseUrl}/properties/list?locationExternalIDs=5002&purpose=for-rent&hitsPerPage=6`);
return {
props: {
// Initial code
propertyForSale: propertyForSale?.hits,
propertyForRent: propertyForRent?.hits,
}
}
}
can you try this please?
return {
props: {
propertyForSale: propertyForSale?.hits || null,
propertyForRent: propertyForRent?.hits || null,
}
}

App working locally but not on production: TypeError: Cannot read property 'titulo_categoria' of undefined

I'm trying to deploy an app using Prismic as CMS and everything works perfectly locally, but once I deploy to vercel I get the error:
19:09:51.850 | TypeError: Cannot read property 'titulo_categoria' of undefined
There seems to be something wrong when it tries to get the data from Prismic.
My code is the following:
import {getAllCategorias, getCategory2} from '../../lib/api';
export default function Index({cat}) {
return <>{cat.titulo_categoria[0].text}</>;
}
export async function getStaticProps({params}) {
const data = await getCategory2(params.slug);
return {
props: {
cat: data?.categorias ?? null,
},
};
}
export async function getStaticPaths() {
const allPosts = await getAllCategorias();
return {
paths: allPosts?.map(({node}) => `/test/${node._meta.uid}`) || [],
fallback: true,
};
}
And the API code that gets data from Prismic is:
import Prismic from 'prismic-javascript';
const REPOSITORY = process.env.PRISMIC_REPOSITORY_NAME;
const REF_API_URL = `https://${REPOSITORY}.prismic.io/api/v2`;
const GRAPHQL_API_URL = `https://${REPOSITORY}.prismic.io/graphql`;
// export const API_URL = 'https://your-repo-name.cdn.prismic.io/api/v2'
export const API_TOKEN = process.env.PRISMIC_API_TOKEN;
export const API_LOCALE = process.env.PRISMIC_REPOSITORY_LOCALE;
export const PrismicClient = Prismic.client(REF_API_URL, {
accessToken: API_TOKEN,
});
async function fetchAPI(query, {previewData, variables} = {}) {
const prismicAPI = await PrismicClient.getApi();
const res = await fetch(
`${GRAPHQL_API_URL}?query=${query}&variables=${JSON.stringify(variables)}`,
{
headers: {
'Prismic-Ref': previewData?.ref || prismicAPI.masterRef.ref,
'Content-Type': 'application/json',
'Accept-Language': API_LOCALE,
Authorization: `Token ${API_TOKEN}`,
},
}
);
if (res.status !== 200) {
console.log(await res.text());
throw new Error('Failed to fetch API');
}
const json = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error('Failed to fetch API');
}
return json.data;
}
export async function getCategory2(slug) {
const data = await fetchAPI(
`
query CategoryBySlug($slug: String!, $lang: String!) {
categorias(uid: $slug, lang: $lang) {
titulo_categoria
_meta {
uid
}
}
}
`,
{
variables: {
slug,
lang: API_LOCALE,
},
}
);
return data;
}
Any idea what's wrong with this? I been trying to figure it out for the whole day without any luck
Perhaps you already checked that, but since you mentioned everything works locally and not on Vercel are you sure your environment variables are set there? Especially PRISMIC_API_TOKEN since it appears you're relying on it to query the API?
Also I'm a bit worried about that part of the code:
props: {
cat: data?.categorias ?? null,
}
...where you might be sending a null value to your Index component resulting in your error, I'd try that instead:
props: {
cat: data?.categorias ?? {},
}
...plus using the safe navigation operator (?.) on the Index component?
Let me know how it goes!

Firebase Firestore returns a promise in Vue

I'm trying to use some data from from Firestore. before it used to work, now in Vuetify I keep getting 'PENDING' if I try to access the $data.users
export default {
data() {
return {
users: [],
};
},
created() {
db.collection('users').get().then((snapshot) => {
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
this.users = user;
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
});
},
methods: {
imageUrl(user) {
console.log(user.documents.selfie.url); // Here the log return "Pending";
},
Inside the template I run a v-for (user, index) in users :key='index'
ERROR:
Uncaught (in promise) TypeError: Cannot read property 'selfie' of undefined
It's difficult to be 100% sure without reproducing your problem, but I think the problem comes from the fact that the Promise returned by the asynchronous get() method is not yet fulfilled when you call the imageUrl() method. This is why you get the pending value.
One possibility to solve that is to check as follows:
methods: {
imageUrl(user) {
if (user) {
console.log(user.documents.selfie.url);
} else {
//...
}
},
Also, is seems you want to populate the users Array with the docs from the users collection. You should do as follows:
created() {
db.collection('users').get().then((snapshot) => {
let usersArray = [];
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
usersArray.push(user);
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
this.users = usersArray;
});
},
With your current code you assign the last user in the loop, not the list of users.

How to save resulted values from firestore into a variable and use it on various components and states in Vue

I'm looking for a way to save the results received from a firestore collection and use it on a variable and reuse the save it to data() and reuse it.
I'm unsure how to save the results from a async function into a variable.
Please help me with this guys
Here is my code.
import firebase from "firebase";
export default{
data() {
return {
uid: firebase.auth().currentUser.uid,
teamLead: ""
};
},
created() {
db.collection("users").onSnapshot(users => {
users.docChanges().forEach(user => {
if (this.uid == user.doc.id) {
this.teamLead = user.doc.data().teamLead
}
});
});
console.log(this.teamLead)
In this code I would like to have the value of teamLead saved into the data() function. How do I do that?
It's a crazy idea to read all user from db. Instead of you can retrieve user by id.
Don't use "this" inside callbacks or lambda.
You have to understand order of execution of async programs. See my comments.
[]
export default{
data() {
return {
teamLead: ""
};
},
created() {
let self = this
db.collection("users").doc(firebase.auth().currentUser.uid).get().then(function(doc) {
if (doc.exists) {
self.teamLead = doc.data().teamLead
console.log(self.teamLead)//<-- this log will executed after firestore return any data
}
});
console.log(this.teamLead)//<-- this log will executed before firestore return any data

Resources