Error retrieving data from Firebase database - javascript

I'm trying to sync my app to a firebase database to retrieve some data. With the code below, I'm able to console log 'polls' which is a basic collection I made in Firebase, and I can see all my data in the console log.
But if I actually try to reach into the polls object using polls.data.id or polls.data.label or even just polls.data, the page blanks out and I get an error saying that it "Cannot read the properties" that I'm calling for. Any help would be greatly appreciated!
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
useEffect(() => {
console.log(polls.data.id);
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
Since my app is clearly fetching the object properly, I'm not sure why I'm not able to actually retrieve the data from within the object to use in my app.

Your polls is an array, so you can't just console.log(polls.data.id). Since polls is an array, it has no data attribute, and thus you can't call id on that, which is what the error says.
Instead, loop over the results with:
useEffect(() => {
polls.forEach((pol) =>
console.log(poll.id);
})
}, [polls])
Note that I also console.log(poll.id), since you map the ID to the object itself, not in its data property.

Related

ReactQuery does not always mark data as changed when refetching

I am currently trying to use react-query to fetch data for use in a react-table. This is what i currently have, i omitted the table stuff for simplicity:
const { data, refetch } = useQuery(['users'], api.user.getAll);
useEffect(() => {
console.log('data changed')
}, [data]);
// this triggers 'data changed'
const createUser = useMutation((user: IUser) => api.user.create(user), {
onSuccess: () => {
refetch();
console.log('refetched')
},
});
// this does not
const updateUser = useMutation((user: IUser) => api.user.update(user), {
onSuccess: () => {
refetch();
console.log('refetched')
},
});
const onCreateClick = () => {
const newUser: IUser = {
id: 0,
userName: 'test',
email: 'test#mail.de'
}
createUser.mutate(newUser);
};
const onEditClick = (user: IUser) => {
user.userName = 'New Name'
updateUser.mutate(user);
};
console.log(data)
// ... render to table
When adding (or removing) a user everything works as expected. However when i update the data of an existing user the useEffect hook that tracks if data changed does not trigger (and for the same reason the react-table does not show the updated values).
The data does get fetched as expected in both cases and the console.log at the end does log the array with the updated values. It almost seems like the data field returned by useQuery does not get marked as changed for arrays if its length doesn't change.
I don't understand this, since this is new data that got fetched from an api and thus should always get treated as changed.
I am using axios under the hood to do the fetching if that is relevant.
What am i doing wrong, any ideas?
I found the issue:
user.userName = 'New Name'
This was a reference to a user inside of data. Never edit the values in data returned by useQuery in place. By doing this the newly fetched data did match the existing one and thus useQuery did not mark it as changed.

iterate through multiple document with certain doc id from a collection

hope you are coding well, I am using firebase and vue js to build a messenger application.
Each user has a document, and within each document there is a field named includeInMsg, which contains the Id of the message that the user included. I know how to read one document by its id, but I don't know how to read multiple documents by their ids.
here you can see my code:
import { ref } from "vue";
import { db } from "src/boot/firebase";
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
import {
addDoc,
collection,
query,
where,
getDocs,
getDoc
} from "firebase/firestore";
import { doc, onSnapshot } from "firebase/firestore";
export default {
setup() {
return {
userList: ref([]),
userMessageRefList: ref([]),
userMessageDocList: ref([]),
freelancerProfile: ref([]),
messages: ref([]),
userMessage: ref([]),
listofdata: ref([])
};
},
async created() {
// check if user loged in or not
const auth = getAuth();
onAuthStateChanged(auth, user => {
if (user) {
// store current user uid in userlist
this.userList.unshift(user.uid);
this.renderMessageList();
} else {
this.$router.push("/");
}
});
},
methods: {
// render user message list doc id
async renderMessageList() {
const currentUserProfile = query(
collection(db, "userProfile"),
where("uuid", "==", this.userList[0])
);
const userProfileQuerySnapshot = await getDocs(currentUserProfile);
userProfileQuerySnapshot.forEach(doc => {
let docData = doc.data();
this.userMessageRefList.unshift(docData.messageList);
this.listofdata.unshift(docData);
});
this.userMessageRefList.forEach(elem =>
// store doc id into userMessageDocList
elem.forEach(le => this.userMessageDocList.unshift(le))
);
console.log(this.userMessageDocList) //output: {0: {…}, 1: {…}}
// now I need to read each document by their doc Id and store it in an array to render
},
},
computed: {
checkMessages: function() {
if (this.messages.length > 1) {
return true;
} else {
return false;
}
}
}
};
I tried to use for loop to read each document and store the value in an array but I got SyntaxError: Unexpected reserved word 'await'. (71:26) error.
As confirmed in the comments, this.userMessageDocList contains a compounded object containing objects with the documents IDs.
In this case you should use Promise.all() to trigger a variable number of calls to Firestore in parallel.
As explained in the doc (see link above):
The Promise.all() method takes an iterable of promises as an
input, and returns a single Promise that resolves to an array of the
results of the input promises.
So you need to loop over the object, grab the Docs IDs and populate an Array of Promises that you pass to Promise.all(). In pseudo code (I let you use the method you want to loop over the object, there are a lot of examples on SO) it's something along the following lines:
const promisesArray = [];
<Loop For each element>
// Assumption: element.docId contains the Firestore doc ID
promisesArray.push(getDoc(doc(db, "yourCollection", element.docId)))
</End Loop>
const docSnapshotsArray = await Promise.all(promisesArray);
docSnapshotsArray.forEach(docSnap => {
console.log(docSnap.data());
// ...
});
docSnapshotsArrays is an Array of DocumentSnapshots. You can then loop over this array and do whatever you want.
I don't now if this is important for your specific case, but note that with Promise.all(), the returned values in the resulting Array will be in order of the Promises passed, regardless of completion order.

Component unable to fetch data from Firebase when navigating to it for the first time or coming back

Background
I'm building an app which displays a number of stores in the home screen. They are shown in a carousel which is filled up with information from a Firestore Collection and Firebase Storage. The user can navigate into each store by pressing on them. The Home Screen display works just fine every single time, but when navigating to one store components come back as undefined. This is the way I'm fetching the data:
export default function StoreDetailMain ({route}) {
const { storeId } = route.params
const [store, setStore] = useState()
useEffect(() => {
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(storeId)
.get()
.then(documentSnapshot => {
console.log('Store exists: ', documentSnapshot.exists);
if (documentSnapshot.exists) {
console.log('Store data: ', documentSnapshot.data());
setStore(documentSnapshot.data())
console.log(documentSnapshot.data())
}
});
}
fetchQuery()
}, [storeId])
Then I'm rendering the information within tags as in <Text>{store.value}</Text>.
Problem
Navigating once to the store will always return a Component Exception: undefined is not an object (evaluating 'store.value'). However if I cut the "{store.value}" tags it works just fine. Then I can manually type them in again and they render perfectly. Once I go back to the Home Screen and try to go into another store I have to do it all again. Delete the calls for information within the return(), save the code, reload the app and type them in again.
What I have tried
Sometimes, not always, Expo will give me a warning about not being able to perform a React state update on an unmounted component. I thought this might be the problem so I gave it a go by altering my useEffect method:
export default function StoreDetailMain ({route}) {
const { storeId } = route.params
const [store, setStore] = useState()
useEffect(() => {
let mounted = true;
if(mounted){
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(storeId)
.get()
.then(documentSnapshot => {
console.log('Store exists: ', documentSnapshot.exists);
if (documentSnapshot.exists) {
console.log('Store data: ', documentSnapshot.data());
setBar(documentSnapshot.data())
console.log(documentSnapshot.data())
}
});
}
fetchQuery()
}
return () => mounted = false;
}, [storeId])
This would not solve the issue nor provide any variation.
Question
Is this due to the unmounting/mounting of components? If so, wouldn't the useEffect method take care of it? If anyone could provide an explanation/solution it would be very much appreciated.
Thanks in advance.
Edit 1:
When the application fails to render the information, it doesn't print into the console the document snapshot. When it can render the data, it does log it. Thus the change in title.
try giving it a initial value
const [ store, setStore ] = useState({value: ''})
or render it conditionally
{ store?.value && <Text>{store.value}</Text> }
secondly, route.params is defined? When you switching screens, did u make sure u pass the params? Switching from stack navigator to tab navigator for example, may drop the params.

How do I properly consume and present cloud function server data via Ajax requests?

I'm trying to consume a json response from an Express style server in Firebase's Cloud functions, but I'm unable to present the response in the DOM. The response path and status (200) are good, but the response data I'm getting in the browser is my entire index.HTML page, not the json data.
Here's my basic set up in the cloud functions:
app.get("/tester", (req, res) => {
res.json({ serverData: "Hello" });
});
exports.app = functions.https.onRequest(app);
and my React FE code to consume it:
function App() {
let I;
const onClick = () => {
axios.get("/tester").then(res => {
I = res.data.serverData;
console.log(I);
});
};
return (
<div>
<button onClick={onClick}>click</button>
<div>{I}</div>
</div>
);
}
Like I said above, the response data I'm getting in the dev tools is just the barebones index.html page, not the text I want to receive. How can I map this data to the DOM?
You need I to be defined as a 'state' property within your function. Easiest way to do that is use React's useState hook. First add this to your script by including:
import React, { useState } from 'react';
Then declare I using the useState hook and, after retrieving the data, use the associated setter to set the new value:
function App() {
let [I, setI] = useState('');
const onClick = () => {
axios.get("/tester").then(res => {
setI(res.data.serverData);
console.log(I);
});
};
return (
<div>
<button onClick={onClick}>click</button>
<div>{I}</div>
</div>
);
}

[TypeError]: Firebase orderByChild not working

I'm trying to query my database such that it retrieves an ordered list based on each child key. I do it as follows (see below), but "TypeError" happens. That is ordered at random when using .on('value', snapshot =>. I can't fix that, do you guys have any ideas to realize?
The Error
TypeError: In this environment the sources for assign MUST be an object. This error is a performance optimization and not spec compliant.
Realtime Database Query
Source Code
export const messagesFetch = (room) => {
return (dispatch) => {
firebase.database().ref(`/rooms/${room.roomId}/messages`)
.once('child_added', snapshot => {
dispatch({ type: 'messages_fetch_success', payload: snapshot.val() });
})
};
};
child_added will create a new snapshot for each message, instead of returning a list of messages in one object.
You might want to look at .once('value') instead of once('child_added').
As you wanted an ordered list I added the query orderByKey() which will return the messages in key order.
export const messagesFetch = (room) => {
return (dispatch) => {
firebase.database().ref(`/rooms/${room.roomId}/messages`)
.orderByKey()
.once('value')
.then(snapshots => {
snapshots.forEach(snapshot => {
dispatch({ type: 'messages_fetch_success', payload: snapshot.val() });
return false;
});
});
};
};
Is a react-native app, right?! If yes add a flag in question.
In firebase 'child_added' fires for each child and ever a new child has add, like sketchthat says. With 'once' you not listen the node, but, because 'child_added' you fires your function for every child. In other words your function return differents values (snapshot) for a same constant.

Categories