Firebase Firestore Guides show how to iterate documents in a collection snapshot with forEach:
db.collection("cities").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
});
I imagined it would support map as well, but it doesn't. How can I map the snapshot?
The answer is:
querySnapshot.docs.map(function(doc) {
# do something
})
The Reference page for Firestore reveals the docs property on the snapshot.
docs non-null Array of non-null firebase.firestore.DocumentSnapshot
An array of all the documents in the QuerySnapshot.
Got pretty sick and tired of Firestore returning stuff in their classes or whatever. Here's a helper that if you give it a db and collection it will return all the records in that collection as a promise that resolves an actual array.
const docsArr = (db, collection) => {
return db
.collection(collection)
.get()
.then(snapshot => snapshot.docs.map(x => x.data()))
}
;(async () => {
const arr = await docsArr(myDb, myCollection)
console.log(arr)
})()
// https://firebase.google.com/docs/firestore/query-data/get-data
const querySnapshot = await db.collection("students").get();
// https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot?authuser=0#docs
querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
Here's another example
var favEventIds = ["abc", "123"];
const modifiedEvents = eventListSnapshot.docs.map(function (doc) {
const eventData = doc.data()
eventData.id = doc.id
eventData.is_favorite = favEventIds.includes(doc.id)
return eventData
})
I have found that a better way to do this by using map and get your document id as well is as follows:
start with the object array I wish to update in your constructor:
this.state = {
allmystuffData: [
{id: null,LO_Name: "name", LO_Birthday: {seconds: 0, nanoseconds: 0},
LO_Gender: "Gender", LO_Avatar: "https://someimage", LO_Type: "xxxxx"},],
};
and in my function do the following
const profile = firebase
.firestore()
.collection("users")
.doc(user.uid)
.collection("stuff")
.get()
.then( async (querySnapshot) => {
console.log("number of stuff records for ",user.uid," record count is: ",
querySnapshot.size);
const profile = await Promise.all(querySnapshot.docs.map( async (doc) => {
const stuffData = doc.data()
stuffData.id = doc.id
condole.log("doc.id => ",doc.id)
return stuffData
}));
this.setState({allmystuffData: profile});
})
.catch(function (error) {
console.log("error getting stuff: ", error);
})
In this example I read all the documents in the collection with querysnapshot the when mapping accross them. the promise.all ensures that all the records are returned before you render it to your screen. I add the document id to the "id" element of each object in the array returned, then I use setstate to replace my state array with the array returned from the query.
you can try this
FirebaseFirestore.instance
.collection('Video_Requests')
.get()
.then((QuerySnapshot querySnapshot){querySnapshot.docs.forEach((doc){
print(doc.data());
});
});
Related
I am trying to order my 'orders' collection in descending order and i am getting this error and i cant seem to figure out why.
const [orders, setOrders] = useState([]);
useEffect(() => {
if (user) {
const userOrders = onSnapshot(
doc(db, "users", user?.id, "orders").orderBy("created", "desc"));
setOrders(
userOrders.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
);
} else {
setOrders([]);
}
}, [user]);
I am new to v9 modular firebase and if someone could help me out with this i'd appreciate it.
--UPDATE--
So i used a hardcoded value for my user?.id and also restructured my code to:
const [orders, setOrders] = useState([]);
useEffect(() => {
const getData = async () => {
if (user) {
const collRef = doc(
db,
"users",
"ZnvtmiBtrafmqHY9AegcwYU2cKV2",
"orders"
);
const docSnap = await onSnapshot(collRef, orderBy("created", "desc"));
setOrders(docSnap);
orders.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
} else {
setOrders([]);
}
};
getData();
}, [user]);
and I am getting this error:
Orders.jsx:41 Uncaught (in promise) FirebaseError: Invalid document reference. Document references must have an even number of segments, but users/ZnvtmiBtrafmqHY9AegcwYU2cKV2/orders has 3.
I am not sure why I am getting this error.
There are several problems in your code:
By doing doc(db, "users", user?.id, "orders").orderBy("created", "desc"); you try to call the orderBy() method on a DocumentReference, which is not possible (and actually this DocumentReference is wrongly defined). You need to call the orderBy() method on a CollectionReference.
You cannot directly call map() on userOrders, since onSnapshot() attaches a listener for DocumentSnapshot events and returns a function that can be called to cancel the snapshot listener.
I would suggest you read the following documentation:
Get data with Cloud Firestore. In your case you should probably use the get() method to to get the data once.
Order and limit data with Cloud Firestore. See the first example and how a Query is defined with const q = query(citiesRef, orderBy("name"), limit(3));.
In your case, the query should be declared as follows:
const collRef = collection(db, "users", user?.id, "orders");
const q = query(collRef, orderBy("created", "desc"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
});
// Or querySnapshot.docs().map(...);
I'm trying to build compound query in Expo react native - firestore.
I have 2 collections in firebase. First "node" is userID and second are IDs of places that had been discovered by this user. Then, I need to take this array of place IDs and pass it as parameter in 2nd query where I got name of each place stored in collection named "databaseOfPlaces". (I want to make scrollable view with names, so maybe I should add listener later on?)
My solution is not working very well. Can you help me? Is this the right way, or is there another way how to save DB call?
Thank you very much.
This is my code:
async componentDidMount() {
db.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const users = [];
snapshot.forEach((doc) => {
const data = doc.data();
users.push(data);
});
this.setState({ users: users });
})
.catch((error) => alert(error));
db.collection("databaseOfPlaces")
.where('placeID','in',this.state.users)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const places = [];
snapshot.forEach((doc) => {
const data = doc.data();
places.push(data);
});
this.setState({ places: places });
})
.catch((error) => alert(error));
}
Data is loaded from Firestore (and most modern cloud APIs) asynchronously. By the time your second query now runs, the results for the first query are not available yet.
Because of this, any code that needs the results from the first query, will need to be inside the then() callback of that query.
So:
async componentDidMount() {
db.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const users = [];
snapshot.forEach((doc) => {
const data = doc.data();
users.push(data);
});
this.setState({ users: users });
db.collection("databaseOfPlaces")
.where('placeID','in', users)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const places = [];
snapshot.forEach((doc) => {
const data = doc.data();
places.push(data);
});
this.setState({ places: places });
})
})
.catch((error) => alert(error));
}
I'm getting data from Firebase and want to update state:
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // is empty
}
);
})
} catch (error) {
console.log('xxx', error);
}
}
The setAllProfile will update the state when the iteration is done. So in order for your code to work, you will need to pass the callback function to the setAllProfile as shown in the docs
setAllProfile((prevState) => [...prevState, nProfile])
UPDATE
Example demonstrating this at work
Since setAllProfile is the asynchronous method, you can't get the updated value immediately after setAllProfile. You should get it inside useEffect with adding a allProfile dependency.
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // Old `allProfile` value will be printed, which is the initial empty array.
useEffect(() => {
console.log(allProfile);
}, [allProfile]);
UPDATE
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
} catch (error) {
console.log('xxx', error);
}
}
You are calling setState inside a map and therefore create few async calls, all referred to by current ..allProfile value call (and not prev => [...prev...)
Try
let arr=[]
querySnapshot.docs.map(doc => {
arr.push({ id: doc.id, doc: doc.data() })
}
setAllProfile(prev=>[...prev, ...arr])
I don't sure how the architecture of fetching the posts implemented (in terms of pagination and so on, so you might don't need to destruct ...prev
I want to have the query with subquery. Lets see the first query:
static async getInitialProps(ctx) {
let product = []
const PostId = ctx.query.pid;
await firebase.firestore()
.collection('products')
.doc(PostId)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
});
return product
})
.catch(error => {
console.log('error', error)
})
return {product: product[0]}
}
now I got the information from that query, but I also need subcollections. I found out how to get all subcollection like this, but then I lose the data from above query.
static async getInitialProps(ctx) {
let product = []
const PostId = ctx.query.pid;
await firebase.firestore()
.collection('products')
.doc(PostId)
.collection('offers')
.orderBy('position', 'asc')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
});
return product
})
.catch(error => {
console.log('error', error)
})
return {product: product[0]}
}
So is it possible to combine this toghether and get all from collection('products').doc(postID) and all from collection('products').doc(postID).collection('offers') ? So all in one query.
No, it's not possible in one query. Firestore queries are shallow and only consider documents immediately within the collection being queried. There are no SQL-like joins between collections. If you need documents from multiple collections, you will have to perform multiple queries.
The only type of query that can get documents from multiple collections is a collection group query, and those only consider documents among all collections with the same name. You could use that to query all documents among all "offers" subcollections, but you wouldn't be able to filter by anything in a parent document.
I am trying to export a firestore function that performs a query and returns an array containing the objects in that query. I am trying to get data from a subcollection of a document, and get an array of document objects returned to render to the client.
I've tried the below but it's not working (e.g. the object returns blank). I think this has to do with improper handling of promises, but couldn't figure it out on my own. Thanks for your help.
export const getEvents = (id) => {
let events = [];
firestore.collection('users')
.doc(id)
.collection('events')
.get()
.then((snapshot) => {
snapshot.forEach((doc) => events.push(doc));
});
return events;
};
You are correct in identifying this problem is related to the handling of promises. You are returning the events array before it has a chance to be populated, because the promise hasn't resolved yet.
If your environment allows it, I would recommend that you use async/await, because it makes the code much easier to read and understand, like this:
export const getEvents = async (id) => {
let events = [];
const snapshot = await firestore.collection('users')
.doc(id)
.collection('events')
.get()
snapshot.forEach((doc) => events.push(doc));
return events;
};
But if you can't use async/await you can do it with promises. But you need to only resolve the promise after the data is fetched:
const getEvents = (id) => {
return new Promise((resolve, reject) => {
let events = [];
const snapshot = firestore.collection('users')
.doc(id)
.collection('events')
.get()
.then((snapshot) => {
snapshot.forEach((doc) => events.push(doc));
resolve(events); // return the events only after they are fetched
})
.catch(error => {
reject(error);
});
});
};