I am trying to fetch data from Firebase Cloud Firestore and load it into my ReactJS app upon loading into the browser.
In this code, I only get the value of ORDER.
How to get both the values ORDER and OrderItems?
componentDidMount() {
firestore
.collection('ORDERS')
.get()
.then((querySnapshot) => {
const ORDERS = [];
querySnapshot.forEach((doc) => {
const data = doc.data();
ORDERS.push(data);
});
this.setState({ ORDERS: ORDERS });
})
.catch((error) => console.log(error));
}
how to get both the values
Fetching items from a sub-collection
You are currently fetching the items from the ORDERS collection. You can take an order document and fetch the orderItems within it by using a reference.
I've used async/await. However, you can easily convert this to use promises.
// Set the reference for the order document
const orderRef = firestore.collection('ORDERS').doc(orderId);
// Fetch the document and extract the data
const orderDS = await orderRef.get();
const orderData = orderDS.data();
// Fetch the orderItems and add them to an array
const orderItemsQS = await orderRef.collection('orderItems').get();
const orderItems = orderItemsQS.docs.map(orderItemQDS => (
const {id, ref, path} = orderItemsQDS; // You can add one or more of these to make it easy to update values back into Firestore
const data = orderItemsQDS.data();
return {id, ref, path, data};
));
You can either fetch these as you use your forEach and push or (recommended) only fetch orderItems when a user has selected an ORDER.
Alternative document references
Option 1
const orderRef = firestore.collection('ORDERS').doc(orderId);
const orderItemsQS = await orderRef.collection('orderItems).get();
Option 2
const orderItemsQS = await firestore
.collection('ORDERS').doc(orderId)
.collection('orderItems').get();
Option 3
const orderItemsQS = await firestore.collection(`ORDERS/${orderId}/orderItems`).get();
Naming conventions
I add the following suffixes to my variable names to make them easier to read:
QS = QuerySnapshot
QDS = QueryDocumentSnapshot
DS = DocumentSnapshot
Javascript reference
This is a really useful resource to help you when getting started.
https://firebase.google.com/docs/reference/js/firebase.firestore
Related
I use the MERN stack and I want to create something like this on the frontend: http://localhost:3000/products?color=0000&sort=latest&category=man etc.
I'm using the redux toolkit and my getProductSlice looks like this:
export const getProducts = createAsyncThunk(
'product/get',
async ({ page, sort, localValue, colorFilter, categoryFilter }, thunkAPI) => {
let colorStr = colorFilter.substring(1);
let url = `/api/v1/products?page=${page}&sort=${sort}&color=${colorStr}&cat=${categoryFilter}`;
if (localValue) {
url = url + `&search=${localValue}`;
}
try {
const { data } = await axios.get(url);
return data;
} catch (error) {
const message = error.response.data.msg;
return thunkAPI.rejectWithValue(message);
}
}
);
Every filter works great, in the backend I return products depending on which filter is applied. I dont know how to build query in frontend and when i enter that url i want to filters to be applied based on url.
To build a query string in the frontend and apply filters based on the query string in the URL, you can do the following:
1 Parse the query string from the URL using the URLSearchParams API:
const searchParams = new URLSearchParams(window.location.search);
Extract the values of the filters from the query string using the get method of URLSearchParams:
const color = searchParams.get('color');
const sort = searchParams.get('sort');
const category = searchParams.get('category');
Use the extracted filter values to create a payload object for the getProducts async thunk:
const payload = {
page: 1, // or whatever page you want to start from
sort,
localValue: '', // or whatever value you want to use for the localValue filter
colorFilter: color,
categoryFilter: category
};
Dispatch the getProducts async thunk with the payload object:
dispatch(getProducts(payload));
This should apply the filters based on the query string in the URL when the getProducts async thunk is dispatched.
Basically, I have two collections per post. One with the comments on the post and one with the post information. The formula I wrote gives me the data of the post information. I want it to return both collections so I can map through the comment collection. but I cannot seem to figure out how to get it to send me to one level up basically. Any help would be appreicated!
const docRef = doc(db, "posts", postId);
useEffect(() => {
if(postId){
const getUsers = async () => {
const data = await getDoc(docRef)
console.log(data.data())
}
getUsers()
}
}, [])
The answer I was looking for is as follows!
const stepOne = collection(db,"posts")
const stepTwo = doc(stepOne,postId)
const stepThree = collection(stepTwo,"comments")
const stepFour = onSnapshot(stepThree,((snapshot)=>snapshot.docs.map((doc)=>console.log(doc.data()))))
Through this structure on the picture, comments are a collection nested inside posts, right? If so, then you cannot return a subcollection from a simple query on the parent document. Try fetching the post info first, then fetch the comments from an other query
this is for posts:
const docRef = doc(db, "posts", postId);
the path for comment will be
"posts/postId/comment"
For the sake of this question let's first assume existence of such entity:
export interface Event {
id: number;
date: Date;
}
Then let's assume there's backend with such endpoints:
GET /events -> returns all events
GET /events?startDate=dateA&endDate=dateB -> returns all events between dateA and dateB
I create hook containing 4 methods (one for each CRUD operation) in my frontend code like this:
export function useEvents() {
const getEvents() = async () => {
const response = await axios.get(`events`);
return response.data;
}
const postEvent()...
const updateEvent()...
const deleteEvent()...
const query = useQuery('events', getEvents);
const postMutation = ...
const updateMutation = ...
const deleteMutation = ...
return { query, postMutation, updateMutation, deleteMutation }
}
This architecture works like a charm but I got to the point where I would like to conditionaly fetch events based on currently chosen month in my Calendar.tsx component.
How would I inject this information into useQuery() and getEvents()?
the query key should contain all "dependencies" that you need for your fetch. This is documented in the official docs here, and I've also blogged about it here.
So, in short:
const getEvents(month) = async () => {
const response = await axios.get(`events/${month}`);
return response.data;
}
const query = useQuery(['events', month], () => getEvents(month));
The good thing is that react-query will always refetch when the key changes, so data for every month is cached separately, and if the month changes, you'll get a fetch with that month.
so I have a problem right now. I'm entering the users dates into cloud firestore like this:
so this is a user collection, with a document by user's id's and then the dates are entered as a list. But whenever I refresh the page and enter new data, all the previous data disappears.
So I'm wondering how do I enter data so that it goes like collection(userCalendar).doc(USERID).collection(dates) and then it has all the user's data entered as strings rather than an array like I've been doing.
My code for the way it's behaving right now is below. Thank you! :)
export const allEvents = [];
const Calendar = () => {
const [date, setData] = useState([]);
const handleDateClick = async (DateClickArg) => {
if (DateClickArg.jsEvent.altKey) {
const title = prompt("Enter title", DateClickArg.dateStr); // allows user to put a title in
// making object
const event = {
title: title ? title : DateClickArg.dateStr,
start: DateClickArg.date,
allDay: true
}
allEvents.push(event)
const db = fire.firestore();
let currentUserUID = fire.auth().currentUser.uid
const doc = await fire
.firestore()
.collection("userCalendar")
.doc(currentUserUID)
.get()
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
}
}
You can use arrayUnion() to add new items to an array however it'll be difficult for you to query activities of a user.
For example, you cannot fetch a single activity from that array but you'll have to fetch all of them get the required one. Additionally, you cannot update an object in an array directly in Firestore.
Also a document has a max size limit of 1 MB so if a user can have many activities, it'll be best to create sub-collection.
I would recommend restructuring the following way:
users -> { userId } -> activities-> { activityId }
(col) (doc) (col) (doc)
All of user's activities/events are now in a sub-collection "activities" and each activity would be a document instead of an array item. With this you can easily read/update/delete a single activity.
Also checkout: Firestore subcollection vs array
Not sure whether this meets your requirement, but from my understanding you just want to update the activities with the allEvents which contains all the updated activities.
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
should become
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
}, { merge: true })
Or you can use the update method
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: allEvents
})
From the docs
To update some fields of a document without overwriting the entire document, use the update() method:
import { doc, setDoc } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
It looks like you're overwriting your collection with every code execution:
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
You should consider to make an array union, so that the values are added to your collection instead of overwriting them:
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: firebase.firestore.FieldValue.arrayUnion(
{
allEvents
}),
})
Also some examples from firestore docu:
https://cloud.google.com/firestore/docs/samples/firestore-data-set-array-operations
i got the following code:
const db = firebase.firestore();
var duettsRef = db.collection("duetts");
export const keyExists = (setKey)=>{
duettsRef.where("key", "==", setKey).where("player2", "==", "").get().then(querySnapshot => {
console.log(querySnapshot);
if (!querySnapshot.empty) {
this.db.collection('duett').set
({
player2: firebase.auth().currentUser.uid,
});
}
})
}
I logged the snapshot, and its working good untill this point.
The "key" is unique, so the snapshot is either empty or finding one document with this specific properties.
Screenshot of my firestore structure.
What is not working, that I want if the snapshot is not empty, to edit the "player2" field in the found document, and set it to the current user id.
So for example, if I search for the key:2jskmd21, it would fill the User ID in the player2 field: Like this.
How do I do this correctly?
For this situation, you will want to use .update() rather than .set(). This is because the update method is designed to update values in a document while set requires a merge: true flag however its primary function is to create a document if it doesn't exist which can be counterproductive.
In this code, I am setting the value as you described and by setting the limit to 1, will retrieve only 1 document.
const db = firebase.firestore();
var duettsRef = db.collection("duetts");
export const keyExists = (setKey)=>{
duettsRef.where("key", "==", setKey)
.where("player2", "==", "").limit(1).get()
.then(querySnapshot => {
console.log(querySnapshot);
if (!querySnapshot.empty) {
querySnapshot.docs[0].ref.update({
player2:firebase.auth().currentUser.uid});
}
})
.catch(e => console.log(e));
}