pistou
pistou

Reputation: 2867

Optimized Retrieve datas grouped by another Collection in Firebase's Firestore

I have two collections in my Firestore databse, equivalent to:

// ItemCategories collection
{
    id_category_1: {
        name: 'Category 1',
    },
    id_category_2: {
        name: 'Category 2',
    },
    id_category_3: {
        name: 'Category 3',
    },
    // ...
}

// Items collection
{
    id_item_1: {
        CategoryId: 'id_category_1',
        name: 'Item 1',
    },
    id_item_2: {
        CategoryId: 'id_category_1',
        name: 'Item 2',
    },
    id_item_3: {
        CategoryId: 'id_category_3',
        name: 'Item 3',
    },
    id_item_4: {
        CategoryId: 'id_category_2',
        name: 'Item 4',
    },
    // ...
}

I'd like to retrieve and format my Items to be separated by categories, as such:

const ItemList = {
    'Category 1': [
        {
            id: 'id_item_1',
            CategoryId: 'id_category_1',
            name: 'Item 1',
        },
        {
            id: 'id_item_2',
            CategoryId: 'id_category_1',
            name: 'Item 2',
        },
    ],
    'Category 2': [
        {
            id: 'id_item_4',
            CategoryId: 'id_category_2',
            name: 'Item 4',
        },
    ],
    'Category 3': [
        {
            id: 'id_item_3',
            CategoryId: 'id_category_3',
            name: 'Item 3',
        },
    ],
};

I'm currently working with a mess of Promises:

// Function to retrieve Items for a given CategoryId
const getItemsByCategory = async CategoryId => {
    const Items = await new Promise(resolve => {
        firebase
            .firestore()
            .collection('items')
            .where('CategoryId', '==', CategoryId)
            .orderBy('name', 'ASC')
            .onSnapshot(querySnapshot => {
                const values = [];
                querySnapshot.forEach(doc => {
                    values.push({
                        ...doc.data(),
                        key: doc.id,
                    });
                });
                resolve(values);
            });
    });
    return Items;
};

// Function to actually get all the items, formatted as wanted
export const getItemList = () => {
    return dispatch => { // I'm in a Redux Action
        const Items = {};

        firebase
            .firestore()
            .collection('itemCategories')
            .orderBy('name', 'ASC')
            .get() // Categories can't be changed
            .then(querySnapshot => {
                const Promises = [];
                querySnapshot.forEach(doc => {
                    const category = doc.data().name;

                    const P = new Promise(resolve => {
                        getItemsByCategory(doc.id).then(values => {
                            const result = {
                                category,
                                values,
                            };
                            resolve(result);
                        });
                    });

                    Promises.push(P);
                });

                Promise.all(Promises).then(values => {
                    values.forEach(v => {
                        Items[v.category] = v.values;
                    });

                    // Here I have the formatted items
                    console.log(Items); 
                    //dispatch(setItemList(Items)); // Redux stuff
                });
            });
    }
};

This code is working, but I feel like there should be ways to optimize it. I can see at least two issues with this:

Is there any better solution than what I got to?

Upvotes: 0

Views: 160

Answers (1)

Doug Stevenson
Doug Stevenson

Reputation: 317477

As Firebase charges every call, I can't see this scaling.

Firestore doesn't charge based on "calls". It charges for number of documents read and amount of data transferred. If you need to read all the categories, then you will just pay for a number of documents read about of that collection. It doesn't matter how many calls it took you to read those documents (the exception being that a query that returns 0 results still incurs one read).

There is not a whole lot of difference, in practice, between waiting for 1 query that returns N documents, or N queries that each return 1 document. You are still just waiting for N documents either way. With Firestore, there are no queries that don't scale well. The limiting factor is always going to be based on the number of documents received, not the number of queries. Your code is going to mostly spend time just waiting for the results to transfer. Of course, there is a practical upper bound to the number of things you can hold in memory, but the number of promises is not going to be a big deal.

Aside from that, I don't understand why you are using onSnapshot in getItemsByCategory. Seems like you just want to use get() instead of receiving just one result in a listener. Your code will be much simpler that way.

Upvotes: 1

Related Questions