Only first element is showing state updates until external event - reactjs

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));
});
});
});

Related

Problems with Snapshot for Firebase, need workaround

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.

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..

react useEffect strange behaviour - can't explain?

I have the following code in a useEffect
useEffect(() => {
async function fetchMessages() {
let messages = [];
const firestore = firebase.firestore();
const query = firestore.collection('chats').where("repliedTo", "==", false).where("type", "==", "StudentQuery").orderBy("timestamp", "desc");
query.onSnapshot({
next: (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log("x ", doc.id, '=>', doc.data());
messages.push({mid: doc.id, ...doc.data()});
console.log(messages)
});
},
});
setMessagesList(messages)
setMessageCount(messagesList.length)
console.log('xxxx' + messagesList.length)
}
fetchMessages();
}, [messagesList.length]);
A few things seem to be wrong with this and I can't see it.
When I trigger this code (by inserting a new record into Firestore) what I expect would be to see a console.log with the (final) array size (so previous array size + 1) - but instead what I am seeing is the previous array + (previous array + 1 entry) I would have thought he let messages = [] would have cleared the array every time an update happened?
I never see the console.log("xxx") in my console. I want to put a state update here as this line should be safe as the database read has done, but since the line doesn't appear I don't know what's going wrong.
Can anyone shed some insight?
I've not used firebase before but it looks like you're effectively creating a subscription which is getting called outside of React's render cycle.
You could just add a state property that you update when next is called, eg:
const [messages, setMessages] = useState([]);
// Use the `useEffect` to set up / tear down the subscription
useEffect(() => {
const firestore = firebase.firestore();
const query = firestore.collection(...);
const unsubscribe = query.onSnapshot({
next: (querySnapshot) => {
setMessages(prev => [
...prev,
...querySnapshot.map(doc => ({
mid: doc.id,
...doc.data(),
})),
]);
});
});
// Unsubscribe when you unmount
return () => unsubscribe();
}, [])

Updating the state correctly after fetch data from firestore

I am trying to use Firestore Snapchats to get real time changes on a database. I am using react-native-cli: 2.0.1 and react-native: 0.64.1 .
export const WelcomeScreen = observer(function WelcomeScreen() {
const [listData, setListData] = useState([]);
const onResult = (querySnapshot) => {
const items = []
firestore()
.collection("Test")
.get()
.then((querySnapshot) => {
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data}
items.push(tempData);
});
setListData(items)
});
}
const onError = (error) => {
console.error(error);
}
firestore().collection('Test').onSnapshot(onResult, onError);
}
Every thing is working perfectly, until I use setListData to update the data list. The App does not respond anymore and I get a warning message each time I try to add or delete data from the database
Please report: Excessive number of pending callbacks: 501. Some pending callbacks that might have leaked by never being called from native code
I am creating a deadlock by setting the state this way?
First, you don't want to set up a snapshot listener in the body of your component. This results in a growing number of listeners, because every time you render you add a new listener, but every listener results in rendering again, etc. So set up the listener just once in a useEffect:
const [listData, setListData] = useState([]);
useEffect(() => {
function onResult(querySnapshot) {
// ...
}
function onError(error) {
console.log(error);
}
const unsubscribe = firestore().collection('Test').onSnapshot(onResult, onError);
return unsubscribe;
}, []);
In addition, your onResult function is going to get called when you get the result, and yet you're having it turn around and immediately doing a get to re-request the data it already has. Instead, just use the snapshot you're given:
function onResult(querySnapshot) {
const items = []
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data()}
items.push(tempData);
});
setListData(items);
}

State not updating until external event

I am using useEffect to run a function that grabs data from firebase.
The data is grabbed fine, however the setState function does not seem to take effect until after another state has changed.... I thought using useEffect would run before first render.
function App() {
const [expenses, setExpenses] = useState([]);
const roster = [];
React.useEffect(() => {
const getRoster = async () => {
var db = firebase.firestore();
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);
});
});
console.log("setting roster");
setExpenses(roster);
console.log("roster", roster);
console.log("expenses", expenses);
};
getRoster();
}, []);
the console returns the following
setting roster
roster [all data from firebase here]
expenses [blank], **expenses is the state variable**
The expenses state only updates after I change some other state in the application. I've tried to work around this by changing some other states in the use effect function, no dice. Also I've tried passing the state as a dependency to the use effect. Nothing...
I must doing something wrong but I'm not sure what that is.
My goal is to have the expenses state updated on first page load.
setExpenses(roster); should be called inside .then as it .get is an async call and it takes some time so within this time your setExpenses(roster); gets called and its has the initial value of roster. So you should use your setExpenses(roster); as bellow
React.useEffect(() => {
const getRoster = async () => {
var db = firebase.firestore();
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);
});
console.log("setting roster");
setExpenses(roster);
console.log("roster", roster);
console.log("expenses", expenses);
});
};
getRoster();
}, []);
The setExpenses call should be placed directly after the querySnapshot.forEach call, but still within the .then((querySnapshot) => { ... } handler. Because it's currently placed after that handler, it is executed immediately on first render, not when the Firebase data is obtained.

Resources