Parsing firebase query data in reactjs - reactjs

I am using firebase cloud firestore for storing data. And I am developing a web app using reactjs. I have obtained documents using the following function:
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
I am getting the desired data, but when I am iterating through persons array, it says it has length of 0.
here is the console output when I am displaying complete persons array and its length.
The length should be 14, but it shows 0. Please correct me what is wrong with me?
I want to display the output in the html inside the render() method of react component.
The output of
const peoples = this.getPeoples();
console.log(peoples);
It is:
The complete render method looks like:
render() {
const peoples = this.getPeoples();
console.log(peoples);
return (
<div className="peopleContainer">
<h2>Post-Graduate Students</h2>
{/* <h4>{displayPerson}</h4> */}
</div>
);
}

This is due to the fact the query to the database is asynchronous and you are returning the persons array before this asynchronous task is finished (i.e. before the promise returned by the get() method resolves).
You should return the persons array within the then(), as follows:
getPeoples() {
let persons = [];
return firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
console.log(persons);
return persons;
});
}
And you need to call it as follows, because it will return a promise :
getPeoples().then(result => {
console.log(result);
});
Have a look at what is written to the console if you do:
console.log(getPeoples());
getPeoples().then(result => {
console.log(result);
});

I'm not sure but please try to update your
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
to
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
persons.push({name = doc.data().Name,
course = doc.data().Course
})
});
console.log(persons);
return persons;
}
Update
Sorry I thought you have problem with filling persons array in your function. Anyway as Renaud mentioned the query in your function is asynchronous so the result is not quick enough to be displayed on render. I use similar function and I found redux the best way to handle this situations.

Related

Trying to join 2 Firestore documents but page freezes

I am trying to join 2 Firestore documents with eachother. I could not really figure out what the proper method was, so I figured something out myself. This is the function:
function getRooms() {
setLoading(true);
roomdb.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setRooms(items);
db.collection("Users")
.doc(user?.uid)
.collection("Lijstjes")
.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setUserRoomId(items);
});
for (var i = 0; i < rooms.length; i++) {
for (var y = 0; y < userRoomId.length; i++) {
if (rooms[i]?.id === userRoomId[y]?.id) {
setUserRooms(...userRooms, rooms[i]);
}
}
}
setLoading(false);
});
}
So what I am basically trying to do here is to only show the rooms that the user has in his document. I loop over all the rooms and in that loop I have another loop to see if the room id is the same one that the user has. What am I doing wrong here?
For reference, here is how I structured my data:
Rooms:
Users:
'Lijstjes' means rooms in my language.
I load this function using the useEffect() function.
Unless your app requires live update of the user's rooms, I don't think this is the correct place for an onSnapshot, let alone a nested onSnapshot!
You can get() a user's rooms given their uid with a simple query, then use those returned room IDs to get the room data in another query.
async function getRooms() { // get() queries are async...
// ...so you need to *await* for the following queries
setLoading(true)
return await db.collection("Users")
.doc(user?.uid)
.collection("Lijstjes")
.get() // returns a an array of user's rooms as DocumentReferences
.then(function (querySnapshot) {
const roomPromises = [] // the array where you'll store the .get() requests for rooms
querySnapshot.forEach(function(docRef) {
roomPromises.push(db.collection("rooms").doc(docRef.id).get())
});
return Promise.all(roomPromises) // execution of each room's .get()
})
.then(function (rooms) {
const roomsData = rooms.map(function (room) { return room.data() })
setUserRooms(roomsData)
})
.then(function () {
setLoading(false);
})
}
The basic flow is:
setLoading to true
get() the document references in the user's Lijstjes subcollection
use those document reference IDs to run get() queries for those specific documents in the rooms collection
return the room documents' data (in this case setUserRooms)
once you have the data you need (after all asynchronous functions have completed successfully) then you can setLoading to false
N.B. the Promise.all() pattern is just a way of running the X number of room get() requests in parallel and is a really great practice for speeding up your async functions. That block could also be run sequentially.

Can't get first item in array Angular

I get some seasons of a series from my API.
After that, I want to use seasons[0] to get the first item in the array.
The problem is that seasons[0] returns undefined.
My Code looks like this :
async ionViewWillEnter() {
const seasons = await this.serieService.fetchCompleteSerie(this.serie);
this.seasons = seasons;
console.log(seasons); //output below
console.log(seasons[0]); //undefined
this.selected = seasons[0]; //undefined
}
my service looks like this:
async fetchCompleteSerie(serie: Serie) {
let allSeasons: any;
let serieSeasons = [];
let allEpisodes: any;
let seasonEpisodes: any;
allSeasons = await this.http.get('https://www.myapp.com/api/seasons/', this.httpOptions).toPromise();
await allSeasons.forEach(async season => {
season["episodes"] = [];
if (season.serie === serie.url) {
allEpisodes = await this.http.get('https://www.myapp.com/api/episodes/', this.httpOptions).toPromise();
allEpisodes.forEach(episode => {
if (episode.season === season.url) {
season.episodes.push(episode);
}
});
serieSeasons.push(season);
}
});
return serieSeasons;
}
The console output looks like this :
Why is it undefined?
The problem is the forEach which DOES NOT RETURN the promises you try to wait for. For that reason seasons[0] is still undefined. But since you log the array to the console and THE SAME array object is used inside your callback, the console refreshes the output after the data arrives. If you clone the array before logging, you will see that its empty console.log([...seasons]);
Simply switch forEach to map and use Promise.all.
async fetchCompleteSerie(serie: Serie) {
let allSeasons: any;
let serieSeasons = [];
let allEpisodes: any;
let seasonEpisodes: any;
allSeasons = await this.http
.get("https://www.myapp.com/api/seasons/", this.httpOptions)
.toPromise();
await Promise.all(allSeasons.map(async season => {
season["episodes"] = [];
if (season.serie === serie.url) {
allEpisodes = await this.http
.get("https://www.myapp.com/api/episodes/", this.httpOptions)
.toPromise();
allEpisodes.forEach(episode => {
if (episode.season === season.url) {
season.episodes.push(episode);
}
});
serieSeasons.push(season);
}
}));
return serieSeasons;
}

ReactJS+FireStore Data mapping issue

Im writing a small program to fetch the categories from the Firestore DB and show in webpage as a list.
My code look like this:
class Category extends Component {
constructor() {
super();
this.state = {'Categories': []}
}
render() {
let categoryList = null;
if (Array.isArray(this.state.Categories)) {
console.log(this.state.Categories);
categoryList = this.state.Categories.map((category) => {
return <li>{category.name}</li>
});
}
return(
<ul>{categoryList}</ul>
);
}
componentWillMount() {
// fetch the data from the Google FireStore for the Category Collection
var CategoryCollection = fire.collection('Category');
let categories = [];
CategoryCollection.get().then((snapshot)=> {
snapshot.forEach ((doc) => {
categories.push(doc.data());
});
}).catch((error) => {
console.log("Error in getting the data")
});
this.setState({'Categories': categories});
}
}
Im able to fetch the data and even populate this.state.Categories, however the map function is not getting executed.
The console.log statement produce an array of values butthe map function in render is not getting executed. Any thoughts?
Console.log output:
You have an error in handling data retrieval. In the last line categories is still empty, so it triggers setState with an empty data set. Should be something lie that
componentWillMount() {
fire.collection('Category').get()
.then(snapshot => {
const categories = snapshot.map(doc => doc.data());
// sorry, but js object should be pascal cased almost always
this.setState({ categories });
})
.catch(error => {
console.log("Error in getting the data")
});
}
Only return the data if the data exists. The simplest way to do this is to replace <ul>{categoryList}</ul> with <ul>{this.state.categories && categoryList}</ul>
I could make it work with a small change (moved this.setState to be inside the callback). Honestly, I still don't understand the difference.
P.S: I come from PHP development and this is my first step into ReactJS.
componentWillMount() {
// fetch the data from the Google FireStore for the Category Collection
var categoryCollection = fire.collection('Category');
let categories = [];
categoryCollection.get().then((snapshot)=> {
snapshot.forEach ((doc) => {
categories.push(doc.data());
});
if (categories.length > 0) {
this.setState({'Categories': categories});
}
}).catch((error) => {
console.log("Error in getting the data");
});
// this.setState({'Categories': categories});
}

Cloud Firestore deep get with subcollection

Let's say we have a root collection named 'todos'.
Every document in this collection has:
title: String
subcollection named todo_items
Every document in the subcollection todo_items has
title: String
completed: Boolean
I know that querying in Cloud Firestore is shallow by default, which is great, but is there a way to query the todos and get results that include the subcollection todo_items automatically?
In other words, how do I make the following query include the todo_items subcollection?
db.collection('todos').onSnapshot((snapshot) => {
snapshot.docChanges.forEach((change) => {
// ...
});
});
This type of query isn't supported, although it is something we may consider in the future.
If anyone is still interested in knowing how to do deep query in firestore, here's a version of cloud function getAllTodos that I've come up with, that returns all the 'todos' which has 'todo_items' subcollection.
exports.getAllTodos = function (req, res) {
getTodos().
then((todos) => {
console.log("All Todos " + todos) // All Todos with its todo_items sub collection.
return res.json(todos);
})
.catch((err) => {
console.log('Error getting documents', err);
return res.status(500).json({ message: "Error getting the all Todos" + err });
});
}
function getTodos(){
var todosRef = db.collection('todos');
return todosRef.get()
.then((snapshot) => {
let todos = [];
return Promise.all(
snapshot.docs.map(doc => {
let todo = {};
todo.id = doc.id;
todo.todo = doc.data(); // will have 'todo.title'
var todoItemsPromise = getTodoItemsById(todo.id);
return todoItemsPromise.then((todoItems) => {
todo.todo_items = todoItems;
todos.push(todo);
return todos;
})
})
)
.then(todos => {
return todos.length > 0 ? todos[todos.length - 1] : [];
})
})
}
function getTodoItemsById(id){
var todoItemsRef = db.collection('todos').doc(id).collection('todo_items');
let todo_items = [];
return todoItemsRef.get()
.then(snapshot => {
snapshot.forEach(item => {
let todo_item = {};
todo_item.id = item.id;
todo_item.todo_item = item.data(); // will have 'todo_item.title' and 'todo_item.completed'
todo_items.push(todo_item);
})
return todo_items;
})
}
I have faced the same issue but with IOS, any way if i get your question and if you use auto-ID for to-do collection document its will be easy if your store the document ID as afield with the title field
in my case :
let ref = self.db.collection("collectionName").document()
let data = ["docID": ref.documentID,"title" :"some title"]
So when you retrieve lets say an array of to-do's and when click on any item you can navigate so easy by the path
ref = db.collection("docID/\(todo_items)")
I wish i could give you the exact code but i'm not familiar with Javascript
I used AngularFirestore (afs) and Typescript:
import { map, flatMap } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
interface DocWithId {
id: string;
}
convertSnapshots<T>(snaps) {
return <T[]>snaps.map(snap => {
return {
id: snap.payload.doc.id,
...snap.payload.doc.data()
};
});
}
getDocumentsWithSubcollection<T extends DocWithId>(
collection: string,
subCollection: string
) {
return this.afs
.collection(collection)
.snapshotChanges()
.pipe(
map(this.convertSnapshots),
map((documents: T[]) =>
documents.map(document => {
return this.afs
.collection(`${collection}/${document.id}/${subCollection}`)
.snapshotChanges()
.pipe(
map(this.convertSnapshots),
map(subdocuments =>
Object.assign(document, { [subCollection]: subdocuments })
)
);
})
),
flatMap(combined => combineLatest(combined))
);
}
As pointed out in other answers, you cannot request deep queries.
My recommendation: Duplicate your data as minimally as possible.
I'm running into this same problem with "pet ownership". In my search results, I need to display each pet a user owns, but I also need to be able to search for pets on their own. I ended up duplicated the data. I'm going to have a pets array property on each user AS WELL AS a pets subcollection. I think that's the best we can do with these kinds of scenarios.
According to docs, you needs to make 2 calls to the firestore.. one to fetch doc and second to fetch subcollection.
The best you can do to reduce overall time is to make these two calls parallelly using promise.All or promise.allSettled instead of sequentially.
You could try something like this:
db.collection('coll').doc('doc').collection('subcoll').doc('subdoc')

how to get id of object from firebase database in reactjs

how to get id of object from firebase database in reactjs
I have an array of list got from firebase database in react-redux , I want to get id of every object of array, How can I get?
Get the snapshot, and iterate through it as a Map, with Object.keys(foo).forEach for example.
Here is a dummy piece of code :
`
const rootRef = firebase.database().ref();
const fooRef = rootRef.child("foo");
fooRef.on("value", snap => {
const foo = snap.val();
if (foo !== null) {
Object.keys(foo).forEach(key => {
// The ID is the key
console.log(key);
// The Object is foo[key]
console.log(foo[key]);
});
}
});
`
Be careful with Arrays in Firebase : they are Maps translated into Arrays if the IDs are consecutive numbers started from '0'. If you remove an item in the middle of your array, it will not change the ID accordingly. Better work with Maps, it's more predictable.
You could try something like this:
export const getAllRooms = () => {
return roomCollection.get().then(function (querySnapshot) {
const rooms = [];
querySnapshot.forEach(function (doc) {
const room = doc.data();
room.id = doc.id;
rooms.push(room);
});
return rooms;
});
};
`

Resources