Problems with Snapshot for Firebase, need workaround - reactjs

I am working on react project and am new to using firebase. Basically I am doing a query to get user's data and I want to reference it in the html but it does not let me because I am using a snapshot. I can't set data from the snapshot of the query to a global variable because I don't think it exists out of the snapshot. Is there a way to get the query's data outside of snapshot?
let usersname = "";
const myFunction = async () => {
let user = window.sessionStorage.getItem("user");
const q = query(accountsCollectionRef, where("username", "==", user));
onSnapshot(q, (snapshot) => {
let queryData = [];
snapshot.docs.forEach((doc) => {
queryData.push({ ...doc.data(), id: doc.id })
})
usersname = queryData[0].name;
})
}
myFunction();
console.log(usersname);

You need to check are response exits, this is how you do it.
onSnapshot(q, snapshot => {
if(!snapshot.empty) {
usersname = snapshot.docs[0].data().name
} else {
usersname = ""
}
})
I just shortened the thing you did in your function. Check are it work, most important is if(snapshot.exists()) this is way you check are response have data. Remember to unsubscribe snapshot.

Related

How do I push the results obtained from Firebase query into an array?

I have been able to retrieve all the necessary objects, however when console.logging them they appear as separate objects and not within an array.
I then tried to place the results into an array, however since they are logged as separate objects, my array ends up having only one object.
How do I proceed further?
The code I currently have:
onAuthStateChanged(auth, (user) => {
if (user) {
const userID = user.uid
const objectDoc = query(collection(db, "objects"), where("user", "==", userID));
const getObjectDoc = async () => {
const querySnapshot = await getDocs(objectDoc);
querySnapshot.forEach((doc) => {
let objectArr = []
console.log("Objects => ", doc.data());
objectArr.push(doc.data())
console.log(objectArr)
});
}
getObjectDoc()
} else {}
})
The QuerySnapshot has a docs property that'll be an array of QueryDocumentSnapshot. You can use map() a new array containing the data like this:
const getObjectDoc = async () => {
const querySnapshot = await getDocs(objectDoc);
const dataArr = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data()
}))
console.log(dataArr)
}
For the original code, you have let objectArr = [] within the loop that'll be empty at the start of every iteration. If you move if out of the loop, that function will work too.

React - Listening for changes, then updating data from firestore

I have a calendar with multiple users, i need to listen for changes and refetch data when someone updates an event.
So, my problem is that when i fetch the events, the listener changes and it ends in a infinite loop for all users.
I dont need to do anything with the docs, i just need to know when one changes, then run my fetch function and rerender the events.
(or if anyone has a good alternative solution i'm all ears!)
const q = query(collection(database, eventDatabase));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const events = [];
querySnapshot.forEach((doc) => {
events.push(doc.data());
});
console.log("Event updated: ", events);
fetchEvents(eventDatabase)
});
My fetch events:
const fetchEvents = async (eventDatabase) => {
const querySnapshot = await getDocs(collection(database, eventDatabase));
let allEvents = []
querySnapshot.forEach((doc) => {
allEvents.push({
...doc.data(),
start: doc.data().start.toDate(),
end: doc.data().end.toDate()
})
});
return allEvents
}
Any tips greatly appreciated..

Only first element is showing state updates until external event

Related and an almost direct extension of my earlier question
I am using useEffect to run a function that grabs data from firebase.
The data is grabbed, however only the first element appears to show the additional information added to the state.
const [expenses, setExpenses] = useState([]);
const roster = [];
var db = firebase.firestore();
React.useEffect(() => {
const getRoster = async () => {
db.collection("Roster")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
var data = doc.data();
data.ID = doc.id;
roster.push(data);
});
// setExpenses(roster);
// console.log("expenses", expenses);
// console.log("roster", roster);
})
This is working as expected, the commented out code was the solution to my previous question.
I added in the code below and experienced my new issue.
.then(() => {
roster.forEach((member) => {
let total = 0;
db.collection("Attendance Entries")
.where("attendees", "array-contains", member.ID)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
total++;
member.total = total;
});
setExpenses(roster);
});
});
});
};
getRoster();
}, []);
The first element shows the "member.total" on the inital load, the others only appear after the state is changed. Not sure why that is the case..
Thank you for the help!
Example of what I am seeing on initial load
I don't quite follow all of your code, but I see a possible issue with the way you enqueue state updates in the forEach loop in the section of code you say you've an issue with. When you enqueue state updates in a loop you must use a functional state update so each enqueued update doesn't blow away (overwrite) the previous state.
You can iterate the roster array you've previously computed and then compute the docs total for each member and mutate the member object, then use a functional state update to append the new member object to the expenses state array.
.then(() => {
roster.forEach((member, index) => {
let total = 0;
db.collection("Attendance Entries")
.where("attendees", "array-contains", member.ID)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
total++;
});
member.total = total;
setExpenses(expenses => expenses.concat(member));
});
});
});

Running firebase request in a foreach but want to wait for the result to proceed (async/await not working)

I'm using a react useEffect hook and inside that hook I'm fetching data in a foreach from firebase nothing weird here and this seems to be working.
I want to wait for foreach and requests to finish so my array will be build and I can use it in my following code. But it seems my code is not waiting before continuing (the console.log inside the foreach is shown last in my console).
useEffect(() => {
const unsubscribe = firebase
.locations()
.once('value')
.then(async snapshot => {
if (snapshot.val() !== null) {
const locationObject = snapshot.val()
const userInformation = []
await Object.keys(locationObject).forEach(element => {
firebase.user(element).once('value', userSnapshot => {
if (userSnapshot.val() !== null) {
console.log('inside location')
userInformation.push({
userId: 1,
name: userSnapshot.val().username
})
}
})
})
console.log('usrInfo', userInformation)
}
})
})
What am I doing wrong here? Any help would be appreciated!
The Object.keys(locationObject).forEach code doesn't return a Promise, so calling await on it won't do anything.
If you want to wait for multiple promises to resolve, you'll typically want to use Promise.all. I'd also in general recommend using DataSnapshot.forEach() instead of Object.keys().forEach() as it maintains the order of the child nodes.
So combined that'd become something like:
const unsubscribe = firebase
.locations()
.once('value')
.then(async snapshot => {
let promises = [];
snapshot.forEach(element => {
promises.push(firebase.user(element.key).once('value'))
})
return Promise.all(promises);
}).then(snapshots => {
const userInformation = []
snapshots.forEach(userSnapshot => {
if (userSnapshot.exists()) {
userInformation.push({
userId: 1,
name: userSnapshot.val().username
})
}
})
console.log('userInfo', userInformation)
})

Get data asynchronously from firebase and set state after the data is reached

I'm using react-native and I want to get data from the firebase realtime database and set state of that data and then only then load the view, I don't want the user to see the data getting pushed and mapped on every load of the chat view.
Here is what I've tried
_getMessages = async () => {
let message_array = [];
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
let message_id = await snapshot.key;
let message_ref = await firebase.database().ref('Message').child(message_id).once('value', async (payload) => {
await message_array.push(payload.val())
})
await this.setState({ messages : message_array })
})
}
And in my componentWillMount is simply call the _getMessages() function like this
componentWillMount = async () => {
await this._getMessages();
}
How can I make sure to set the state of messages after getting all the messages from the firebase?
This won't work:
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
Firebase's on() method starts actively listening for events. It does not have a clear moment when it's done, so doesn't return a promise. Hence it can't be used with await/async.
My feeling is that you're trying to simply load all user messages, which is easiest to do by using once("value":
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
let message_array = await ref.once('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})
If you want to get realtime updates, you will have to use an on() listener. But that does mean you can't use async/await in the outer listener. It'd look something like this:
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
ref.on('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})

Resources