Home > Enterprise >  In my custom React hook, Is it possible to write a function that can return a valid firebase query d
In my custom React hook, Is it possible to write a function that can return a valid firebase query d

Time:01-08

For my React application, i am attempting to write a custom hook to be used for querying a Firestore database in Firebase.

So far i wrote a hook that uses a pre-defined query which works. What i cannot figure out is how to write a function capable of dynamically generating that query.

Here is what i have so far:

import { useState, useCallback } from 'react';
import { initializeApp } from "firebase/app";
import { getFirestore, getDocs, collection, query, where, orderBy, limit } from 'firebase/firestore';

import firebaseConfig from "./firebase.config";

const useQuery = () => {
  
  // NOTE for clarity i am showing initialization of firebase here
  // although this actually happens in another file and is imported.
  const firebaseApp = initializeApp(firebaseConfig);
  const firestore = getFirestore(firebaseApp);

  const firestoreRef = collection(firestore, 'myCollection');
  
  const [snapshot, setSnapshot] = useState([]);

  const getSnapshot = useCallback(async(queryConstraints:any) => {
  
  // instead of this pre-defined query ...
  const firestoreQuery = query(firestoreRef,
    where('someField', '>=', 'someValue'),
    where('anotherField', '==', 'anotherValue'),
    orderBy('timestamp', 'asc'),
    limit(10)
  );
    
  // ... Id like to be able to take the incoming argument
  // titled 'queryConstraints' of which its value
  // would be an object like this one:
      //  {[{
      //   where: 'someField',
      //   operator: '>=',
      //   value: 'someValue'
      //  },{
      //   where: 'anotherField',
      //   operator: '==',
      //   value: 'anotherValue'
      //  }], 10, 'timestamp', 'asc'}

  // ... and pass it to a function, lets call it 'configAValidQuery' which
  // would parse the object and return a valid query to be applied as follows:

  //  const myQuery = await configAValidQuery(queryConstraints);
  //  const firestoreQuery = query(firestoreRef, myQuery);

    try {
      const docs = await getDocs(firestoreQuery);
      if(docs.empty) {
        // TO DO: HANDLE EMPTY RESULT
      } else {
        setSnapshot(doc.data());
      }
    } catch(error:any) {
      // TO DO: HANDLE ERROR
    }

  }, [firestoreRef]);

  return {
    snapshot,
    getSnapshot
  }
}

export default useQuery;

I am having trouble figuring out how to construct what basically amounts to a comma delimited list of function calls dynamically, given that the query constraints consist of the imported firebase methods where, orderBy and limit.

The function i need would look something like the following, although this would obviously not work:

const configAValidQuery = useCallback((conditions:any, limit:number, orderBy:string, direction:string) => {
      let query:any;
      if(conditions.length > 0) {
        conditions.map((item:any) => {
          return(query = query.where(item.where, item.operator, item.value))
        });
        query = query.orderBy(orderBy, direction);
        if(limit > 0) {
          query = query.limit(limit);
        }
        return query;
      } else {
        query = query.orderBy(orderBy, direction);
        if(limit > 0) {
          query = query.limit(limit);
        }
        return query;
      }
  },[]);

The firebase/firestore package does not appear to contain a pre-defined interface or function for this purpose.

Any suggestions?

CodePudding user response:

maybe something like this could be nice if you're using typescript

import {
  query,
  where,
  orderBy,
  limit
} from "firebase/firestore";



type Clause = "where" | "limit" | "orderBy";
type Operator = "==" | "!=" | "<" | ">" | "<=" | ">=";

interface WhereArgs {
  column: string;
  operator: Operator;
  value: string | number;
}

interface OrderByArgs {
  column: string;
  order: "asc" | "desc";
}

interface LimitArgs {
  amount: number;
}

type Command =
  | ({ clause: "where" } & WhereArgs)
  | ({ clause: "orderBy" } & OrderByArgs)
  | ({ clause: "limit" } & LimitArgs);

const options = {
  where: (args: WhereArgs) => {
    const { column, operator, value } = args;
    return where(column, operator, value);
  },
  limit: (args: LimitArgs) => {
    const { amount } = args;
    return limit(amount);
  },
  orderBy: (args: OrderByArgs) => {
    const { column, order } = args;
    return orderBy(column, order);
  }
};

const commands: Array<Extract<Command, { clause: Clause }>> = [
  {
    clause: "where",
    column: "anotherField",
    operator: ">=",
    value: "someValue"
  },
  {
    clause: "where",
    column: "anotherField",
    operator: "==",
    value: "anotherValue"
  },
  {
    clause: "limit",
    amount: 10
  },
  {
    clause: "orderBy",
    column: "timestamp",
    order: "asc"
  }
];

const constraints = [];

for (const command of commands) {
  const { clause, ...rest } = command;
  constraints.push(options[command.clause](rest as any));
}

query(null as any, ...constraints);

here is a link to it

CodePudding user response:

Firstival 'query Constraints' in your question is not an object i don't know what it is.

I assume this object should look like this:

const qConstrains = {
        where: [
            { val1: 'fieldName', operator: '>=', val2: 'valueToCompare'},
            { val1: 'anotherfieldName', operator: '>=', val2: 'valueToCompare'}
        ],
        orderBy: { timestamp: 'timestamp', direction: 'asc'},
        limit: 10
    }

Well object above is bad because query function use QueryConstraints[] and it is an array not an object. It is an array witn no objects but functions so all you need to do is make an array of objects so you can save it in database and when using it convert it to an array of functions.

Here is an example of correct array:

const qConstrains = [
        { where: 'fieldName', operator: '>=', value: 'valueToCompare'},
        { where: 'anotherfieldName', operator: '>=', value: 'valueToCompare'},
        { orderBy: 'timestamp', direction: 'asc'},
        { limit: 10 }
    ]

Function converting from array of objects to array of functions:

function convertArrayObjectsToArrayFunctions(queryConstains: any[]) {
    let newQueryConstains: any[] = []
    for(let item of queryConstains) {
        if(item['where']) { // Check are item contains variable name where
            // ... is an spread operator it works same as 
            // Object.assign() in older versions of JavaScript.
            // You can use .push and .unshift i just 
            // wanna forget about that methods.
            newQueryConstains = [ ...newQueryConstains, 
                where(item.where, item.operator, item.value)]
        }
        if(item['orderBy']) {
            newQueryConstains = [ ...newQueryConstains, 
                orderBy(item.orderBy, item['direction'] ? item.direction : undefined)]
        }
        if(item['limit']) {
            newQueryConstains = [ ...newQueryConstains, limit(item.limit)]
        }
    }
    return newQueryConstains
}

Shorter example function above:

function convertArrayObjectsToArrayFunctions(queryConstains: any[]) {
    let newQueryConstains: any[] = []
    for(let item of queryConstains) {
        item['where'] ? newQueryConstains = [ ...newQueryConstains, where(item.where, item.operator, item.value)] : null
        item['orderBy'] ? newQueryConstains = [ ...newQueryConstains, orderBy(item.orderBy, item['direction'] ? item.direction : undefined)] : null
        item['limit'] ? newQueryConstains = [ ...newQueryConstains, limit(item.limit)] : null
    }
    return newQueryConstains
}

Example of usage:

// Do not forget to use ... operator when creating a query.
const q = query(collection(db,'posts'),
   ...convertArrayObjectsToArrayFunctions(qConstrains))

Home work children! XD function above not contain all of functions there can occur. your job is to add extra if statements for: 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'

Important: If you get an error about indexes just ctrl click on link inside error it will setup indexes for you in firestore.

  •  Tags:  
  • Related