How can I access sub collection within every document in collection? - reactjs

I want an user to be able to select different types of food that will be displayed on wall (only 8 items from collection) by changing category in my app.
I have two ideas how to do that but I do not know if they are good.
First is to get items from firestore only on selected category. Example:
const foodRef = db.collection("food").doc('${category}').collection('items');
and just get those items and then set state with those items.
This is how I do it by now and it is working good but I think it is not good because every time user change the category components must re-render and user is making unnecessary queries to firestore.
So I thought that it would be good to get all "items" from every document and store it in an object and then operate on that object based on selected category.
My problem is that I tried to get sub-collection of every document in collection but I actually do not know how to do that.
I tried something like that:
const getItems = async () => {
let snapshot = await firebase.firestore().collection('food').get();
return snapshot.docs.map(doc => doc.collection('items').forEach(item => item.data()))
}
I know it will not gonna work but I wanted to do something like that.
Structure in my firestore looks like that:
food => [16 documents] => items => [8 documents]
So how should I do that guys, get all the items at once or render every time user change category?
Thanks for your time and help in advance guys:)

You can use firestore collectionGroup to get multiple subcollections in different documents. Take a look at this link for details about collection groups.
https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html
In your case, you could probably do something like this:
const everyItems = await firebase.firestore().collectionGroup('items').get();
Let me know if you want more details :)

Related

How can I retrieve a really large data from firestore with a lot of collections and subcollections that need to be displayed separately?

I am pretty new to flutter and firestore, but I got a project I need to make. The app I am making has profiles and each profile displays different data. For example an user has x amount of clients and each client has x amount of different type of information. That being said in my mind I have to have users as the base and for each client to make a lot of sub-collections. However as I’m pretty new to this I don’t exactly know how to implement that in the UI and code behind it. If somebody can point me in some direction, that would be great.
Firestore allows you to paginate your data using cursors.
This is an example From The Docs using the web SDK but the principle is the same:
import { collection, query, orderBy, startAfter, limit, getDocs } from "firebase/firestore";
// Query the first page of docs
const first = query(collection(db, "cities"), orderBy("population"), limit(25));
const documentSnapshots = await getDocs(first);
// Get the last visible document
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
const next = query(collection(db, "cities"),
orderBy("population"),
startAfter(lastVisible),
limit(25));
In terms of sub collections, each doc you retrieve will have a reference property which can be used to create nested subcollection calls instead of writing a function with the fully built path string.

What's the most efficient and scalable way to handle filtering and sorting of a large collection in cloud firestore?

I have a large collection where each document is of a substantial size and therefore cannot be embedded into a single document. For my feature, I need to sort the collection using orderBy and filter the collection using "array-contains" and "==" where the sort and filter parameters are provided by the user. I was wondering if there was an efficient way to do this by cacheing documents that had already been fetched in previous queries. That being said, does firebase do any cacheing itself and does it already optimize what I'm trying to do in this case, or is there any custom cacheing/optimization I can do?
This is what my implementation looks like right now. This works fine for now however it's not very scalable as it creates a new realtime listener each time any of the filter/sort state changes. Is there any way can I improve this to minimize the total number of documents read?
useEffect(() => {
if (!sort || !status || !search) return;
const unsubscribe = firebase.collection("users")
.where("searchTerms", "array-contains", search)
.where("status", "==", status)
.orderBy(sort)
.onSnapshot((snapshot) => {
// update state hook with snapshot data...
});
return () => unsubscribe();
}, [sort, status, search]);
Thank you for any and all help!
Typically, you will want to use a search indexer to enable functionality like this.
The Firebase documentation recommends using Algolia for full-text search. You want to create a cloud function that indexes the data you want to search on, along with the Firestore document ID. On the frontend, you can use the Algolia API to get search results and then fetch the whole document From Firestore when you need to display it.
An alternative to pagination for "big data" and still supporting complex queries can be done by baking the data for the client into a simplified search collection.
This displays only key information that represents a source document by combining the essential data into a dedicated collection of all the results.
Each document can hold up to 1MB of data each and that can equate to roughly 10k-200k entries based on your data size. It does take some time to set up but it has been effective for handling long-lived data within firebase without additional 3rd party solutions.
The key takeaways are as follows:
This is ideal for data that doesn't update too frequently, multiple changes at once can hit the 1 second limit per document.
All search documents contain two properties, a counter to maintain the current entries and an array of strings that represent your essential data.
Each source document needs to maintain a document ID of its entry document for future updates
On update, you find the search index ID, and use arrayUnion and arrayRemove methods, preferably with a transaction and update the source document.
Optionally, you can use the new Bundle Method to bundle this collection with your app
Resources:
https://firebase.google.com/docs/functions/firestore-events#event_triggers
https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
https://firebase.googleblog.com/2021/04/firestore-supports-data-bundles.html

How to re-fetch single items of a collection in react-query

Let's say i have a query to fetch a collection of movies:
useQuery(['movies'], getMovies)
Now, if I want to re-fetch only one movie instead of all I can write something like this:
useQuery(['movies', movieId], () => getMovie(movieId))
Problem is that I use a different query key and that it will duplicate the data. I'll have the movie in my cache twice.
So, what's the react-query way of updating a single item of a fetched collection? All components that use useQuery(['movies']) should be updated automatically when the single item was fetched.
I think there are two ways:
like you described with a new key. yes, the data will be "twice" in the cache, but oftentimes, the "list" data has a different structure than the "detail" data. movies-list might just have id and name, while the details have a description etc. as well. If you do that, make sure to give them both the same prefix ('movies'), so that you can utilize the fuzzy matching when invalidating: queryClient.invalidateQueries(['movies']) will then invalidate the list and all details, which is nice.
you can use the select option to build a detail query on:
const useMovies = (select) => useQuery(['movies'], getMovies, { select })
const useMovie = (id) => useMovies(movies => movies.find(movie => movie.id === id))
with that, you can do useMovie(5) and thus will only subscribe to the movie with id 5. This is really nice and also render optimized - if the movie with id 4 updates, the component that subscribes to movie with id 5 will not re-render.
the "drawback" is of course that for the data / network perspective, there is only one query - the list query, so everytime you do background refetches, the whole list will be queried from the backend. But it's still a nice approach to avoid data duplication in the cache, and it makes optimistic updates easier as well, because you only have to write to update one cache entry.

Can you update firestore query dynamically based on router location?

So basically, I have a simple React app connected to Firebase that lists different types of food from firestore collections.
Example:
I have a few categories. The default one is "All" that displays top 8 popular dishes from all other available categories and this part is easy but I want an user to be able to click on other category and update my query.
Category is actually a NavLink that updates location on click so: if user click on "Pizza" category the url looks like this localhost:3000/Pizza if he clicks on Salad it is localhost:3000/Salad etc.
I have a "Wall" component that is a section and it displays those items from firestore.
My query ref in this wall component look like this: const foodRef = db.collection("food").doc("all").collection("items");
But I want to set .doc dynamically and make query on every update so I changed the query to something like that:
const location = useLocation();
const foodRef = db.collection("food").doc('${location.pathname}').collection("items");
And when user click on different Card (NavLink) url updates but query does not.
I know it is a bad solution but I actually have no idea how to do that.
I have read about Recursive Paths in react router but I do not know if it is what I am looking for.
If you know how to approach that please let me know.
Thanks for your time.
Firestore does not support wildcards or replacements in queries and Query objects are fully immutable (they can't be changed). You have to know the names of the documents and collections ahead of time to build a query. If you want to change some part of a query, you have to rebuild a whole new query object every time, and run the query again to get a new set results.

Connection attributes in firebase query React

I would like to get books from Firebase where author or title is given with React. Sth like that :
fire.firestore().collection('books').where('name OR author', '==', elFromMyState).
get().
then(snapshot => {
snapshot.forEach(doc => {
let element = doc.data();
array.push(element)
})
setBooks(array);
props.setList(array);
})
but don't know how to do it correctly
Unfortunately, firebase provides limited support for logical OR queries.
You can specify multiple value comparisons (logical OR's) for the same field. For example, looking for either Poland OR Austria in a neighboringCountries field.
You cannot specify multiple fields against which you query a single value. For example, looking for the value Nikola Tesla on fields people OR inventors.
The limitations are explained in more detail in the firebase docs: https://firebase.google.com/docs/firestore/query-data/queries#query_limitations
They explain that in order to work around this, you execute two independent queries, then combine the results on the front end for display.

Resources