Firebase data only loading after logging in and out - javascript

I am having an issue with loading data with firebase and react native. When I initially go to the screen no data is shown. Although, if I go back to the login screen and then log back in the data I was looking for is shown.
The code is as follows :
For the useeffect hook:
const ChatRooms = ({navigation}) => {
const [data, setData] = React.useState([]);
const [selectTitle, setTitle] = React.useState(new Map());
useEffect(() => {
const chatTitles = [];
const chats = firebaseSDK.loadChatRooms();
for(let i=0; i <chats.length; i++){
chatTitles.push({title: chats[i]});
}
setData(chatTitles);
}, []);
For the loadChatRooms :
loadChatRooms(){
const chatrooms = firebase.database().ref();
let check = [];
chatrooms.orderByKey()
.on("value", (data) => {
check = Object.keys(data.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
return check;
};
I am struggling to find the issue. I am assuming it has something to do with firebase/useeffect interaction.
Thank you! :)

The problem is that you fail to handle the fact that data is loaded asynchronously.
The easiest way to show how to fix this is by putting the code that loads the data into yoru ChatRooms:
const ChatRooms = ({navigation}) => {
const [data, setData] = React.useState([]);
const [selectTitle, setTitle] = React.useState(new Map());
useEffect(() => {
const ref = firebase.database().ref();
ref.orderByKey().on("value", (data) => {
const chatTitles = [];
chats = Object.keys(data.val());
for(let i=0; i <chats.length; i++){
chatTitles.push({title: chats[i]});
}
setData(chatTitles);
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
}, []);
By moving the code that processes the data into the on() callback, it gets executed once the data has loaded. A fun side-effect of this, is that it also will update the state whenever the data changes in the database.
You can give this a try by changing one of the titles in your database, and watching the UI update in near realtime. And this will of course also work once you have multiple users, all writing messages to the database. 🎉
A slight improvement is to iterate over the results with the built-in forEach operator, which ensures the order will be consistent on all platforms (and also when you order on other properties):
const ref = firebase.database().ref();
ref.orderByKey().on("value", (data) => {
const chatTitles = [];
data.forEach((child) => {
chatTitles.push({title: child.val()});
});
setData(chatTitles);
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});

Related

My async await method for fetching docs in Firestore using getDocs() returns an empty array

My async await method for fetching docs in Firestore using getDocs() returns an empty array.
I'm using React.js.
Thing is, this fetching-data-function is placed within a useEffect() hook with an empty array [] as a dependency (so that it runs, and hence fetches data from the Firestore database only once), and right after, the data is console logged. Output is an empty array.
allData: []
But if I just somehow get the useEffect() hook to run once more (like making a tiny change in the code and saving it - essentially just refreshing it on the local host), the array is populated with the desired data from the database.
This is the code:
import db from "./firebase";
useEffect(() => {
console.log("use effect ran");
const temp = async () => {
const q = query(collection(db, "blogs"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
const newblog = {
id: doc.id,
title: doc.data().title,
content: doc.data().content,
};
setAllData((prev) => [...prev, newblog]);
});
};
temp();
console.log("allData: ", allData);
}, []);
I can't quite figure out what the issue is. I'd be grateful for some help.
You are seeing a empty array in allData because the console.log("allData: ", allData) is running before than setAllData((prev) => [...prev, newblog]) because the async function temp doesn't run in the same thread so the interpreter continues reading the code that follows. To fix it add await to temp() like this: await temp() and wrap it like I do in the following code.
import db from "./firebase";
useEffect(() => {
console.log("use effect ran");
(async () => {
const temp = async () => {
const q = query(collection(db, "blogs"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
const newblog = {
id: doc.id,
title: doc.data().title,
content: doc.data().content
};
setAllData((prev) => [...prev, newblog]);
});
};
await temp();
console.log("allData: ", allData);
})();
}, []);

Keep getting values from function with realtime listener

I am having a bit of trouble setting this up.
I have a folder that deals with all the Db API, so that concerns are separated.
I have one function that opens a connection and gets realtime updates whenever a value in the Db changes (Firebase Firestore).
I call this "listener" function once and would like to keep receiving the real time values within the function that invokes the "listener" function.
Any ideas how I could achieve this?
This is my code:
// LISTENER FN
export const getConnectionRequestsFromDb = () => {
const uid = getUID()
const q = query(
collection(database, "inbox"),
where("uids", "array-contains-any", [uid]),
where("type", "==", "connectionRequest"),
limit(50)
)
const data = []
const unsubscribe = onSnapshot(q, (querySnapshot) => {
// Initially return an empty array, milliseconds later the actual values
querySnapshot.forEach((doc) => data.push(doc.data()))
})
const formattedData = convertDatesIntoJsTimestamps(data)
return [formattedData, null, unsubscribe]
}
// INVOKING FN
export const getConnectionRequests = () => {
return async (dispatch) => {
dispatch({ type: "CONNECTIONS_ACTIONS/GET_CONNECTIONS_REQUEST/pending" })
// I want to keep listening for realtime updates here and dispatch payloads accordingly
const [data, error, unsubscribe] = getConnectionRequestsFromDb()
if (data) {
return dispatch({
type: "CONNECTIONS_ACTIONS/GET_CONNECTIONS_REQUEST/fulfilled",
payload: data,
})
}
}
}

Why isn't onSnapshot listener working when exported to another file

I had an onSnapshot listener function set up, but once I extracted it to another file it stopped working.
The code below is in the utilities file.
export const getUserCampsites = () => {
const user = store.getState().authReducer.user
let campsitesArr = [];
//check if user is signed in
if(user.hasOwnProperty('uid')){
const campsites = db
.collection('campsites')
.where('owner', '==', user.uid);
const unsub = campsites
.onSnapshot(snapshot => {
snapshot.forEach(el => {
campsitesArr.push(el.data());
})
});
return {unsub, campsitesArr}
}
}
And this is what I have in the component:
const [camps, setCamps] = useState();
useEffect(() => {
const res = getUserCampsites()
if(res) {
const campsites = res.campsitesArr
setCamps(campsites);
return(() => res.unsub())
}
}, [user])
When 'setCamps' in the component is called the array comes back empty, but is then filled on a second re-render, am I missing something? Is it supposed to be asynchronous?
Thanks in advance!
The array campsitesArr in the getUserCampsites function is not reactive, so it is not re returned if its value changes.
When the getUserCampsites function is called, it inializes the campsitesArr to an empty arr and then it initializes the listener.. However it does not wait for the listener to fetch values before it moves on to the next line which is the return statement.
So whenever the return is reached, the campsitesArr is still enter, therefore you always return an empty array.
The best solution will be to pass a function as an argument to the getUserCampsites, that receives an array of camp sites. This function will be called whenever the listener gets new values.
For example, you can pass a function to the getUserCampsites that calls setState with the new array of camp sites
Not sure what was wrong, I think it was because the function was returning before the campsitesArr had been populated.
To fix this I passed the setCamps function as a prop and used it directly inside the onSnapshot call back:
export const getUserCampsites = (setCamps) => {
const user = store.getState().authReducer.user
//check if user is signed in
if(user.hasOwnProperty('uid')){
const campsites = db
.collection('campsites')
.where('owner', '==', user.uid);
const unsub = campsites
.onSnapshot(snapshot => {
let campsitesArr = [];
snapshot.forEach(el => {
campsitesArr.push(el.data());
})
setCamps(campsitesArr)
});
return unsub
}
}

react firestore snapshot not a function

Hi so I followed firebase docs to get data from my collection. But I get error that snapshot is undefined.
This is what I tried:
const [data, setData] = useState([]);
useEffect(() => {
const db = Firestore.firestore();
// let tempData = Controller.getData();
const projects = db.collection('projects');
const snapshot = projects.get();
let fireData = [];
snapshot.forEach((doc) => {
console.log('data:', doc.data());
fireData.push(doc.data());
});
setData(fireData);
});
I tried TypeError: snapshot.forEach is not a function
But then I het data() is undefined plz help
projects.get() returns a promise that resolves with a QuerySnapshot object. It doesn't directly return the snapshot. You have to wait for the promise to resolve in order to get the data, as illustrated in the documentation:
projects.get().then(snapshot => {
let fireData = [];
snapshot.forEach((doc) => {
console.log('data:', doc.data());
fireData.push(doc.data());
});
setData(fireData);
});

How to add new objects to an existing useState array using map() or foreach()?

I'm trying to create a Twitter clone, and I'm having troubles with my news feed. Basically it pulls tweets from my firebase database for each user followed by the current user. So say you follow Jon Abrahams and Terry Crews, for each of these 2, it'll pull the "tweets" collection, and for each tweet, it'll return the data.
I did this with useState and useContext ( since I needed Context and couldn't make it work in a class component, but also needed state ).
const CurrentUser = useContext(CurrentUserContext);
const [tweets, setTweets] = useState({tweets: []});
const feedTheFeed = async (id) => {
const followedUsers = await getFollowedUsers(id);
if(followedUsers) {
followedUsers.docs.forEach(async doc => {
const followedId = doc.data();
const unformatTweets = await getTweetsByUser(followedId.follows);
if(unformatTweets.docs) {
unformatTweets.docs.map(unformatTweet => {
const tweetText = unformatTweet.data();
setTweets({
tweets: [...tweets, tweetText]
})
// console.log(tweetText);
})
}
})
// console.log(tweets);
}
}
useEffect(() => {
if(!CurrentUser) return;
if(CurrentUser.id || CurrentUser.uid) {
feedTheFeed(CurrentUser.id);
}
}, [CurrentUser]);
The problem is that there's an issue when loading the component, it says that "tweets is not iterable", but it's an array, so I don't see why it wouldn't work. Does anyone have an idea ?
Thank you !
Seems like what you want is
const [tweets, setTweets] = useState([]);
and
setTweets([...tweets, tweetText])
I think what you want to do is this.....
const [tweets, setTweets] = useState([]);
setTweets(b => [...b, tweetText])

Categories