Background
I'm building a "Liked Stores" screen for an app I'm developing. This screen renders a flatlist which shows cards with each store a user has liked. The way I'm handling the process is that when a user likes a store, it creates a new doc in firestore which holds the id of the store and a "liked" boolean.
Problem
I've been trying to filter the totality of stores in my db and match the id's of the liked stores with the id's in the "liked" collection documents.
The data structure is:
user/uid/favorites, each doc here contains 2 fields. An id (matching to another id in the stores/ collection) and a boolean indicating that it has been liked. And then stores/'s docs contain lots of data but what matters here is a field which holds an id.
I've tried implementing the following solution, with two subscribers bringing each collection and a function which handles the filtering. But for some reason I don't quite understand I just can't get it to work.
Code is as follows:
const [stores, setStores] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('stores/')
.onSnapshot(querySnapshot => {
const stores = [];
querySnapshot.forEach(documentSnapshot => {
stores.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setStores(stores);
});
return () => subscriber();
}, [])
const [liked, setLiked] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('users/' + userId + '/favorites')
.onSnapshot(querySnapshot => {
const liked = [];
querySnapshot.forEach(documentSnapshot => {
liked.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setLiked(liked);
});
return () => subscriber();
}, [])
const usefulData = [];
const handleFilter = (obj) => {
for(let i = 0; i < stores.length; i++){
if(obj.id === liked[i].storeId) {
usefulData.push(obj)
}
}
}
stores.filter(handleFilter);
And then I'm passing usefulData as the data param to the FlatList.
When printing usefulData it simply returns an empty array.
Question
Can anyone provide a solution and an explanation as to why this is not working and how to compare two arrays of objects' values properly?
Well, figured it out in the most uncomfortable and least elegant possible way so any tips or better ideas will still be fondly accepted:
const likedIds = [];
liked.forEach(o => likedIds.push(o.id));
const storeIds = [];
stores.forEach(o => storeIds.push(o.id));
const intersection = storeIds.filter(element => likedIds.includes(element));
const [data, setData] = useState([]);
const finalArray = [];
useEffect(() => {
for (let i = 0; i < intersection.length; i++){
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(intersection[i])
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
finalArray.push(documentSnapshot.data())
}
});
setData(finalArray)
}
fetchQuery()
}
}, [])
Without changing anything else from what is posed in the question, this is what I added in order to get this working. First I get all the Id's out of each array of objects, then filter them and store them in a new array. Then we can go ahead and build a new array of objects using a handy fetchQuery async function to get all the data from the original stores collection, just that this time it only brings up the stores with ids in the intersection array.
I am perfectly aware that this is the most bodged out and disgusting possible solution, but at the very least it works. Hope someone smarter than me can get some help out of this and make my solution much better!
Related
I'm trying to filter all users except current user liked people. liked is an array. allUsers is an array too. I have filtered that if user is logged in, he can't see him self in date app displayed users. But filter out those users which user already liked, it's harder.. There's my code:
useEffect(() => {
async function currentUser() {
const resp = await get(`user/${localSecret}`)
setCurrent(resp.data)
}
currentUser()
}, [])
useEffect(() => {
async function fetchUsers() {
const resp = await get(`api`)
// Filter out current user from displayed users
const filter = resp.filter(user => user.secret !== localSecret)
getAllUsers(filter)
}
fetchUsers()
}, [])
useEffect(() => {
async function getAllUsers() {
const resp = await get('api')
// Here I'm trying to filter out already liked users
// current.liked is an array of users objects
const filtered = resp.filter(user => user !== current.liked)
setAllUsers(filtered)
}
getAllUsers()
}, [])
This is mongodb user liked array of objects, to make simplier to understand:
You can try to filter with the ID of current.liked like so
const filtered = resp.filter(user => !current.liked.includes(user))
setAllUsers(filtered)
if I'm getting what you want to achieve correctly this should work. you can use underscore to always verify its object properties. it's hacky but should work. you can also move this logic to a useCallback
Im trying to just use a empty array in a react/ts project like this.
const [companyChatrooms, setCompanyChatrooms]: any = useState([]);
I then use a useEffect to get it done when rendering component.
async function fetchMyChatRooms() {
const userCollection = await firestore.collection('user_in_chat')
const snapshot = await userCollection.where('user_id', '==', myIdNumber).where('chatroom_id', '==', companyChatrooms).get();
snapshot.forEach(doc => {
const roomID = doc.data().chatroom_id
setMyChatrooms([...myChatrooms, roomID])
});
}
fetchMyChatRooms()
}, [companyChatrooms, myIdNumber])
console.log(myChatrooms)```
However, my console.log shows 2 arrays with each value instead of 1 array holding both values.
How can i make sure both values are stored in same array?
[1]: https://i.stack.imgur.com/q0WPD.png <-- Check how the output looks.
I assume you have an array snapshot with more than 1 element and any iteration you are updating the state. This caused multiple re-render
I suggest you to update state after iterate entire array. Example:
const rooms = []
snapshot.forEach(doc => {
const roomID = doc.data().chatroom_id;
rooms.push(roomID);
});
setMyChatrooms(rooms)
you should set all of them in one time.
async function fetchMyChatRooms() {
const userCollection = await firestore.collection('user_in_chat')
const snapshot = await userCollection.where('user_id', '==', myIdNumber).where('chatroom_id', '==', companyChatrooms).get();
// here is the changing
const roomIDs = snapshot.map(doc => doc.data().chatroom_id);
setMyChatrooms(roomIDs )
//
fetchMyChatRooms()
}, [companyChatrooms, myIdNumber])
console.log(myChatrooms)
I know there is a similar question that exists here but it doesn't really answer the question on here. My code currently on get the last two documents in a collection.
const [recipedata, setRecipeData] = useState([]);
const fetchRecipes = async () =>{
const response = fire.firestore().collection("recipes");
const data = await response.get();
data.docs.forEach(item=>{
setRecipeData([...recipedata, item.data()])
})
}
useEffect(() => {
fetchRecipes();
}, [])
Again when I print (recipedata) it only show two objects in the array. Can anyone tell me why this might be happening?
The query is correct and should fetch all the documents in the collection. You should check the the collection again. Also modifying the code like this should be better so you are updating the recipes array only once.
const [recipedata, setRecipeData] = useState([]);
const fetchRecipes = async () =>{
const response = fire.firestore().collection("recipes");
const data = (await response.get()).docs.map(doc => doc.data());
console.log(data)
//Updating only once
setRecipeData(data)
}
useEffect(() => {
fetchRecipes();
}, [])
Please share your collection as well so we can try replicating any issue if it has something to do with Firestore.
I have been trying to push or store a FireStore array in one of my own arrays. I have tried a few versions of code, the first being this:
var data = [];
db.collection('Rooms')
.doc(code)
.get()
.then((docs) => data.push(docs.data()));
However, when I log the data variable, it comes out as an empty array. The second method I have tried is this:
var [data, setData] = useState([]);
db.collection("Rooms")
.doc(code)
.get()
.then((docs) => setData(docs.data()));
However this method seems to setData infinitely, so it is reading into my API infinitely, which I would like to avoid. The last method I tried was this:
var data = db.collection("Rooms").doc(code).get();
console.log(data);
But this just returns
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
}
Could anyone help me with this, ideally I'd like to store the data of an array called "MovieArray" inside the document, but I can't even access the document, so even if you can just help me store the data of the whole document, it would be very helpful.
If you are using react, I would suggest using the hook. You also, don't really need to push objects to an array like that.
Here is an example of how to get some data and store the collection of data.
const Forum = () => {
const [posts, setPosts] = useState(null);
const collectIdsAndDocs = (doc) => {
return { id: doc.id, ...doc.data() };
};
useEffect(() => {
const getPost = async () => {
const snapshot = await firestore.collection('Posts').get();
const myPosts = snapshot.docs.map(collectIdsAndDocs);
console.log(myPosts);
setPosts({ myPosts });
};
const createPost = async (post) => {
const docRef = await firestore.collection('Posts').add(post);
const doc = await docRef.get();
console.log(doc);
};
createPost({ Title: 'My First Post', Content: 'My content' });
getPost();
}, []);
return (
// return some JSX
);
};
Why does this work?
When you get a collection, Firebase returns a snapshot of the collection.
This snapshot has a list of docs or an array if you will.
We then want to map over those docs constructing a new object that contains just the document data and the ID of individual doc. This is what the myPosts variable is.
Using the react state hook, you can set that object to the current state of Posts, in your case this would be rooms.
When you add something to the database, Firestore will return a reference to the newly added item. You can then call get() to get the document back if you need it.
Try changing to (see comment before this)
const [data, setData] = useState({});
Hi I have something like this that works great:
const handleFavour = () => {
const { developers } = state;
const results = developers.filter(list =>
list.uid.includes("authid")
);
setSearchResults(results);
console.log(results);
};
But now insead of "authid" if i wanted to search using another object or array that has many authids, how do I do this search? Couldn't find anything after searching for a very long time.
If you want to check if any element in an array matches any element in another array you could combine your current solution with .some.
const handleFavour = () => {
const allowedIds = ['id1', 'id2'];
const { developers } = state;
const results = developers.filter(list =>
list.uid.some(uid => allowedIds.includes(uid))
);
setSearchResults(results);
console.log(results);
};
Here .some will return true if any of the elements in list.uid are also present in allowedIds.
Based on your situation as described in the comments it looks like you want something like this:
const handleFavour = () => {
const users = [{ auth: 'id1' }, { auth: 'id2' }];
const { developers } = state;
const results = developers.filter(list =>
users.some(user => user.auth === list.uid)
);
setSearchResults(results);
console.log(results);
};
I initially thought list.uid was an array since you were using .includes, generally I'd recommend using the strict equality operator (===) to compare strings unless you actually want to allow partial matches, but I'm guessing in this case you don't.