How to sort createdAt field from firestore v9 - reactjs

Im new to firebase. I want to retrieve all the data via userId then sort the createdAt field to desc order but it doesnt work for me.
Any help is greatly appreciated.
useEffect(() => {
const user = JSON.parse(localStorage.getItem('user'));
const q = query(collectionRef, where('userId', '==',user.uid),orderBy('createdAt','desc'));
onSnapshot(q, (snapshot) => {
const documents = snapshot.docs.map((doc) => {
return {
...doc.data(),
doc_id: doc.id
};
});
setIsLoading(false);
setTodos(documents);
});
}, []);

Your problem most probably comes from the fact that Firestore misses the composite index corresponding to your query.
Since you didn't add an error callback in your onSnapshot() method call, you cannot see the error. Adding a callback will firstly allow you to confirm that it is a problem of missing composite index and, secondly, show you an URL (in the error message) that will allow you to automatically build this index.

Related

How can I update a nested field in a firestore database?

I am creating a clothing e-commerce application integrated with woocommerce and Firestore.
I currently am trying to build the 'add to wishlist part, here I am struggling with trying to update the items 'favourite' field in the database.
I present my Firestore"
my Firestore database
I have access to the item on my react native side
I need to be able to iterate through the objects, compare the nested object items ID against the idea of the item I am currently clicking on and change the favorite field to true.
Currently, I have tried to do the following, but to no avail.
const like = (item) => {
// db.collection("users").doc(user).collection("wishlist").doc(random).set({
// id:item.id,
// name:item.name,
// })
db.collection("users")
.doc(user)
.collection("products")
.doc("0")
.get()
.then((data) => {
const info = data.data();
});
};
In order to perform an update to an object that exists in an array-type field, you need to find that particular object first. Unfortunately, there is no way you can query a Firestore collection based on a value that exists in an object that is contained in an array. This kind of filtering cannot be achieved using partial data. To solve this, you have to read the array, find the desired elements that need to be updated, perform the update and then write the document back to Firestore.
I have also written an article called:
How to update an array of objects in Firestore?
so at the end I came up with a solution which I thought I would share here:
const like = (item) => {
const newData = { ...Data };
if (newData !== null) {
let index = newData.Products.findIndex((e) => {
return e.id === item.id;
});
console.log(index);
newData.Products[index].favourite = true;
db.collection("users")
.doc(userID)
.collection("products")
.doc("0")
.set(newData)
.then((data) => setData(data))
.catch((error) => console.log(error));
} else {
alert("An error has occured");
}
};
so this takes the data, saves it, adds the particular change and sets it again

Firestore where filter and add data to the result

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));
}

how to save a document to a different collection by reference in firestore in react

This is a problem with either promises or finding a different way to solve the problem.
I have a user, that makes a document about their capabilities e.g.
price:
Bio:
called (contractorPage)
And i have users that look at these.
How do i make it so the users can save these by reference?
using firestore, can you make it so that you add these documents to a collection, but when he original document is changed these are also changed?
my first method is to save the Id of the contractorPage. Which is the user ID (UID) of the firebase auth of the person that made it.
Then To map through these to get all of the saved "id's" documents.
const [ idList, setIdList ] = useState([
])
const [ contractorList, setContractorList ] = useState([
])
useEffect(()=>{
/// IdList has the list of id's
//me.uid is the firebase auth user ID
firestore.collection("SavedId's").doc(me.uid).collection("id").get()
.then( async (querySnapshot) => {
await querySnapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
setIdList([...idList, doc.data()]);
console.log("first",idList)
});
console.log("second",idList)
// contractorList has the list
idList.map((data, index) => (
firestore.collection("contractorPages").doc(data.id).get().then((word)=>{
console.log("hi", word.data())
if (word.exists) {
setContractorList([...contractorList, word.data()])
}
})
)
);
})
},[])
This does not work, because the idList.map() function runs before setIdList([...idList, doc.data()]); has finished.
How do I make sure the idList is set before I try to retrieve data from firestore with what is in it?

React Redux FireStore - Changes in FireStore collection does not reflect on my screen unless I refresh

I use React with Redux and Firebase. Here is one of the functions from my Action.js
export const loadItemsInCategory = (categoryId) => {
return (dispatch) => {
let itemsArray = [];
firestoreService.getItemsInCategory(categoryId)
.then(updatedGroceryList => {
itemsArray = updatedGroceryList;
console.log(`category id is ${categoryId}`)
dispatch(loadItemsInCategoryHelper(categoryId, itemsArray))
})
.catch((error) => console.log(error));
}
}
It's a normal FireStore query. Here is what happens in firestoreService.getItemsInCategory(categoryId)
export const getItemsInCategory = async (categoryId) => {
console.log(`firebase category id is ${categoryId}`)
const snapshot = await db.collection('Item').where('category', '==', categoryId).get()
return snapshot.docs.map(doc => {console.log("called");return {id: doc.id, ...doc.data()}});
}
Right now, my application shows the list of items in the given Category. However, the list does not get updated when a new Item is added to the category by someone else. In other words, additions in FireStore collection does not reflect on my screen unless I refresh the page.
How can I code my webapp in such a way that any change on the FireStore end gets reflected on my webapp?
Thanks in advance!
Your code is doing a one-time query with get(). Queries made like this are not realtime. They don't refresh.
If you want to receive updates to your query in realtime, you should follow the documentation for realtime queries. Instead of using get(), you will use onSnapshot(). And instead of getting a promise, you will attach a listener callback that will be invoked whenever there is a change in the results of the query. Because of these differences, your code will look drastically different.

Lookup multiple Firebase entries using Redux action

I have this Redux action, which looks up a user and returns all the items they have:
export const itemsFetch = () => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items`)
.on('value', snapshot => {
dispatch({ type: ITEMS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
Works great, and each item returned has a unique key associated with it.
I want to modify this action to look up specific items, which I've done. That works fine too:
export const itemLookup = uid => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${uid}`)
.on('value', snapshot => {
dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: snapshot.val() });
});
};
};
This also works fine, but I can only use this to lookup a single item.
I want to loop over an array of item ids, and lookup details for each. Doing this from a component, and using mapStateToProps, causes the component to rerender each time, losing the previous lookup in the process.
Is it best to loop over the ids I have at a component level, and make multiple calls. Or should I pass the array to the action, and somehow loop over them within the action?
Thanks
I feel like I'm doing something dumb, or misunderstanding Redux completely.
In my opinion, this is one of the few limitations that firebase has (along side with queries) that sometimes make me want to grow hair again and lose it (I am bald).
I am more experienced with Firestore although I have used Database, but I think you are correct that you can only request one item in Firebase. What I would do to solve this, is to create a new action that receives an array of IDs and then executes and array of promises that will query each doc.
Something like (pseudo code, and you might need to wrap your firebase call into a promise):
let promises = [];
arrayIds.forEach(id => {
promises.push(firebase.database().ref.get(id))
})
return Promise.all(promises).then(dispatch(results))
Now, if you find that the amount of results are usually not a lot, it is totally fine (and usually the way Firebase requires you to) to complete the data filtering in the client.
Using the response from sfratini, I managed to work it out. Here's the action:
export const itemLookup = oid => {
const { currentUser } = firebase.auth();
const items = []
return dispatch => {
new Promise(function (res, rej) {
oid.forEach(id => {
firebase.database().ref(`users/${currentUser.uid}/items/${id}`).on('value', snapshot => {
items.push(snapshot.val())
})
})
}).then(dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: items }))
}
I used the same reducer, and now the items array makes it down to component level. Perfect!

Resources