Firebase Version 9 using multiple conditional where clauses

I’m updating a project from Firebase Version 8 to Version 9. I have conditional filtered queries that used to be structured as follows:

let query = db.collection("collectionName").orderBy("timestamp")

if (filters.selectedStaff) {
    query = query.where("id", "==", filters.selectedStaff.id);
    }
if (filters.selectedStudent) {
    query = query.where("studentIDs", "array-contains", filters.selectedStudent.id);
    }

Then the query gets executed in a hook that rerenders everytime the filters change. Using Version 8, this works perfectly.

In version 9, queries are now build to follow a format where each query is passed as a parameter to the query function. A normal query would look like:

query(collection(db, "collectionName"), where("id", "==", filters.selectedStaff.id), where("studentIDs", "array-contains", filters.selectedStudent.id), orderBy("timestamp"))

You can still store the where function as a variable, and pass that variable as a parameter to the query function:

let w1 = where("id", "==", filters.selectedStaff.id)
let w2 = where("studentIDs", "array-contains", filters.selectedStudent.id)
query(collection(db, "collectionName"), w1, w2, orderBy(timestamp))

But, the problem I haven’t figured out how to solve, is how to make where clauses conditional. It doesn’t seem like firebase allows a where clause value to be any. For example, making w1 default to:

let w1 = where("id", "==", "*")

If you try to make the operator also a variable and default to anything other than ==, like != :

let w1 = where("id", "!=", "")

Firebase forces you to set the primary sort by the field in the where clause which won’t working if you’re trying to sort by another field like I am (timestamp).

Ultimately, the workaround that works is to create a field in each document that has the same value, like a boolean true value and then set all your where clauses to be equal to that initially, and then dynamically change:

let w1 = where("randomField", "==", true)
let w2 = where("randomField", "==", true)
if(filters.selectedStaff){
    w1 = where("id", "==", filters.selectedStaff.id)
}
if(filters.selectedStudent){
    w2 = where("studentIDs", "array-contains", filters.selectedStudent.id)
}
query(collection(db, "collectionName"), w1, w2, orderBy(timestamp))

While this works, it feels like a really unnecessary workaround and I wanted to see if anyone know of a better way to accomplish the same outcome.

Answer

You can keep using the existing logic, it’s only the syntax that has changed. Try refactoring the code as shown below:

let q = query(collection("db", "collectionName"), orderBy("timestamp")); 

if (filters.selectedStaff) {
  q = query(q, where("id", "==", filters.selectedStaff.id));
}

if (filters.selectedStudent) {
  q = query(q, where("studentIDs", "array-contains", filters.selectedStudent.id));
}

const snapshot = await getDocs(q);

Another approach would be to conditionally push those conditions in an array:

const conditions = [orderBy("timestamp")]


if (filters.selectedStaff) {
  conditions.push(where("id", "==", filters.selectedStaff.id));
}

if (filters.selectedStudent) {
  conditions.push(where("studentIDs", "array-contains", filters.selectedStudent.id));
}

const q = query(collection(db, "collectionName"), ...conditions);
// Do note the spread operator                     ^

const snapshot = await getDocs(q);