Firebase: Problem getting data from nested onSnapshot - reactjs

I have the following useEffect set up to fetch some data from firebase to populate a flatlist
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
// This works, but doesn't load the additional data I need.
// list.push({ ...{ id: doc.id }, ...doc.data() });
// This doesn't seem to load anything.
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list.push({ ...{ id: doc.id }, ...doc.data(), ...documentSnapshot.data() });
});
});
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
});
}, [currentAccountId, currentShopId]);
Here's the problem: my flatlist doesn't load anything. When I initially load the screen, that console.log outputs an empty array. If I force it to re-render by saving the screen file, the array is correctly populated.
As noted in that comment, if I just use list.push({ ...{ id: doc.id }, ...doc.data() }); instead of db.collection('sensors')... it loads the sensors fine, but without the additional data that I need.
How do I resolve this? Thanks in advance!
Solution: As #Rajitha Udayanga has pointed out, checking the length of the first querysnapshot against the list array is a great way to check whether or not all the data has been loaded. Here's the working code (note the conditional that setSensorsData has been moved into):
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list.push({ ...{ id: doc.id }, ...doc.data(), ...documentSnapshot.data() });
// Fix is here:
if (querySnapshot.docs.length === list.length) {
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
});
});
});
}, [currentAccountId, currentShopId]);

Disclaimer: I have zero experience with React.
In Angular I deal with such issues using RxJS's combineLatest.
However, you can try this one:
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
const useValues = () => {
if (list.some(value => value === undefined)) {
return; // not everything has loaded yet - abort
}
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
querySnapshot.forEach((doc, index) => {
list.push(undefined);
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list[index] = ({ id: doc.id, ...doc.data(), ...documentSnapshot.data() });
useValues();
});
});
});
}, [currentAccountId, currentShopId]);
And don't forget to detach the listeners when you don't need them anymore.

useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
const newData = {
...{ id: doc.id },
...doc.data(),
...documentSnapshot.data()
};
const itemIndex = list.findIndex(item => item.id ===
newData.id);
if (itemIndex !== -1) {
list[itemIndex] = newData;
} else {
list.push(newData);
}
// do it like thi
if (querySnapshot.docs.length === list.length) {
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
});
});
});
}, [currentAccountId, currentShopId]);

Related

React does not rerender on updated state of nested array

I have an array of objects like so:
const [categories, setCategories] = React.useState([
{
id: 1,
title: 'Top Picks',
subTitle: "Today's hottest stuff",
images: [],
searchQuery: 'shoes',
},
...]);
Which I update with values in useEffect once like so:
React.useEffect(() => {
const newCategories = categories.map(category => {
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
});
return category;
});
setCategories(newCategories);
}, []);
however the child components here never rerender and I cannot figure out why. My understanding is that .map creates a new array anyhow, so the spread syntax isn't necessary in setCategories() but regardless it does not work.
{categories.map((category, i) => (
<CategorySlider {...category} key={i}/>
))}
There's a few issues but the primary issue I see is you're returning the category before the fetch can complete - so even when those fetch calls inside your map complete, you already returned the category below before the fetch completes.
Try using the .finally() block:
React.useEffect(() => {
const newCategories = categories.map(category => {
const c = {...category}; // <--- good practice
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
}).catch((err) => {
console.error(err);
}).finally(() => {
return category;
});
});
setCategories(newCategories);
}, []);
Thanks! Using setState before the promises resolved was indeed the problem. The solution looks like this now:
React.useEffect(() => {
async function fetchImages() {
const promises = categories.map(async category => {
const response = await fetch(
`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`,
{
headers: {
Authorization: apiKey,
},
}
);
const convertedJson = await response.json();
category.images = convertedJson.photos;
return category;
});
setCategories(await Promise.all(promises));
}
fetchImages();
}, []);

Can't fetch all data from Firestore React-Native

I can't fetch all data from Firestore in my react native app. I can fetch data when I add a limit(200) parameter but when I take away the limit parameter my app crashes. I need to display all the data and I don't know what I'm doing wrong, here is my code:
const listingsRef = firebase.firestore().collection(ServerConfiguration.database.collection.LISTINGS);
if (categoryId === '') {
console.log('Kommer vi hit???');
return listingsRef
.where('isApproved', '==', isApproved)
.limit(2000)
.onSnapshot((querySnapshot) => {
const data = [];
if (querySnapshot !== undefined) {
querySnapshot.forEach((doc) => {
const listing = doc.data();
if (favorites && favorites[doc.id] === true) {
listing.saved = true;
}
data.push({ ...listing, id: doc.id });
});
console.log('KOMMERR datan? : ', data);
callback(data);
}
});
}
I have tried to use get instead of on Snapshot, and I get the same results. I have been stuck for days!
You can replace the limit with getting and getting all the records that satisfy your condition.
const listingsRef = firebase.firestore().collection(ServerConfiguration.database.collection.LISTINGS);
if (categoryId === '') {
console.log('Kommer vi hit???');
return listingsRef
.where('isApproved', '==', isApproved)
.get()
.onSnapshot((querySnapshot) => {
const data = [];
if (querySnapshot !== undefined) {
querySnapshot.forEach((doc) => {
const listing = doc.data();
if (favorites && favorites[doc.id] === true) {
listing.saved = true;
}
data.push({ ...listing, id: doc.id });
});
console.log('KOMMERR datan? : ', data);
callback(data);
}
});
}
But its always a good idea to paginate your results when you have large amount of data

Read Data from Firebase and save it into an Array - React

i just can't figure out why i can't save my loaded data into an array.
i`m trying to push the data to the array once the data is fully loaded (Within then())
Any idea why it's not working?
Many thanks :)
useEffect(() => {
fetchData = async () => {
let tempArray = [];
await firebase.firestore().collection('users').get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
})
console.log(tempArray) // Output: Array []
}
fetchData();
}, [])
.forEach IS NOT ASYNCHRONOUS - it WILL NOT wait for your inner-loop .get()s. You need to do something like:
await firebase.firestore().collection('users').get().then((querySnapshot) => {
Promise.all(querySnapshot.map((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
)
})
In addition - this seems pretty dang inefficient - since you're fetching ALL your users, and ALL their posts, you could just use a collectionGroup is a SINGLE round-trip, then sort by .parent if you need sorting (you don't show any such need in the example presented)
await firebase.firestore()..collectionGroup('posts').get().then((querySnapShot) => {
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
Finally, you're mixing async/await with .then() syntax, which is generally not recommended:
// .get() is asynchronous
const querySnapShot = await firebase.firestore()..collectionGroup('posts').get();
// push() is synchronous, so need for await
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})

Render array's nested object values react

Here is how my savedLinksData array prints in my console:
Here is my code that attempts to get the linkType value:
{savedLinksData.map((saved) => {
return <h1>{saved.linkType}</h1>;
})}
What I'm doing wrong?
I think there may be something wrong with the way I stored the values in the array. It doesn't look like the value are inside of the array.
Picture of console.log("----->", saved)
const [savedLinksData, setSavedLinksData] = useState([]);
// query for saved links data
useEffect(() => {
if (user) {
async function fetchData() {
const request = await db
.collection("users")
.doc(user)
.collection("saved")
.onSnapshot((snapshot) =>
setSavedLinks(
snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
)
);
}
fetchData();
} else {
setSavedLinks([]);
}
}, [user]);
useEffect(() => {
if (savedLinks.length > 0) {
let newArray = [];
savedLinks.map((saved) => {
db.collection("users")
.doc(saved.savedUser)
.collection("links")
.doc(saved.savedLinkId)
.get()
.then(function (doc) {
if (doc.exists) {
// console.log("Document data:", doc.data());
newArray.push(doc.data());
// setSavedLinksData([...savedLinksData, doc.data()]);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch(function (error) {
console.log("Error getting document:", error);
});
});
setSavedLinksData(newArray);
}
}, [savedLinks]);

implement the onSnapshot function for getting realtime updates in reactjs code

in my case I want to implement the onSnapshot function for getting realtime updates, so here's my sample code it's working now:
db.collection("Cities").onSnapshot((snapshot) => {
snapshot.docs.forEach((doc) => {
console.log(doc.data());
});
});
But now, how can I implement it to the following code
getMyInfo = () => {
db.collection("Cities")
.limit(8)
.get()
.then((docs) => {
if (!docs.empty) {
let AllCities = [];
docs.forEach(function (doc) {
const city = {
id: doc,
...doc.data(),
};
AllCities.push(city);
});
this.setState(
{
cities: AllCities,
},
() => {
this.setState({
isLoaded: true,
});
}
);
}
});
};
Thanks
You are saving all your cities into your state. Use it to add or update the new (or updated) cities.
Maybe you can try something like that :
db.collection("Cities").onSnapshot((snapshot) => {
snapshot.docs.forEach((doc) => {
const updatedCity = doc.data();
const cities = [...this.state.cities];
const index = this.state.cities.findIndex(c => c.id === updatedCity.id);
if (index >= 0) {
cities[index] = updatedCity;
} else {
cities.push(updatedCity);
}
this.setState({ cities });
});
});

Resources