DEV Community

Cover image for Firestore with react-query-firebase
Dennis kinuthia
Dennis kinuthia

Posted on

Firestore with react-query-firebase

In the previous article we set up authentication and now we're
ready to add the business and UI logic.
link to previous project

first things first we need

npm i @react-query-firebase/firestore dayjs date-fns 
react-day-picker uniqid

npm i -D @types/dayjs @types/uniqid"

Enter fullscreen mode Exit fullscreen mode

repo link
setting up firebase emulator
react-query-firebase docs page

tips for this project

react-query-firebase has many hooks for all kinds of operations
finding the right one is as important as solving the problem

for example

const query = useFirestoreQuery(["projects"], ref,{
subscribe:true
});

const snapshot = query.data;

return snapshot.docs.map((docSnapshot) => {
  const data = docSnapshot.data();
  return <div key={docSnapshot.id}>{data.name}</div>;
});
Enter fullscreen mode Exit fullscreen mode

this is the hooks for querying a collection with the optional subscribe setting for realtime updates , it's off by default.
This hook returns a snapshot which can be full of classes we don't directly need in our project and makes it really hard to manually manage cache on data mutation.

Luckily they have a hook to fetch only the data which was exactly what i needed

const query = useFirestoreQueryData(["projects"], ref,{
subscribe:true
});

return query.data.map((document) => {
  return <div key={document.id}>{document.name}</div>;
});
Enter fullscreen mode Exit fullscreen mode

like i mentioned earlier react-query does some smart cache management in the backrground to ensure a query isn't ran unless if the data at hand is stale

there's a function invoked on mutate to append the new item to te cache until the next refetch to avoid refetching after every mutation as you'll notice in the code

  const id = uniqid();
  const ProjectRef = doc(db, "projects", id);
  const mutationProject = useFirestoreDocumentMutation(
    ProjectRef,
    { merge: true },
    {
      onMutate: async (newProject) => {
        // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
        await queryClient.cancelQueries("projects");
        // Snapshot the previous value
        const previousTodos = queryClient.getQueryData("projects");
        // Optimistically update to the new value
        //@ts-ignore
        queryClient.setQueryData("projects", (old) => [...old, newProject]);
        // Return a context object with the snapshotted value
        return { previousTodos };
      },
      // If the mutation fails, use the context returned from onMutate to roll back
      onError: (err, newTodo, context) => {
        //@ts-ignore
        queryClient.setQueryData("projects", context.previousTodos);
      },
      // Always refetch after error or success:
      onSettled: () => {
        queryClient.invalidateQueries("projects");
      },
    }
  );

Enter fullscreen mode Exit fullscreen mode

you'll also notice that am using uniqid to getrate my own data ids,it's easier when you have to update the data so it's wise to store it as part of te saved document since firebase generates the deafault ones when you mutate using add() server-side and you'll only have access to them when querying .
the is is also in the top level of the snapshot response so useFirestoreQueryData will not hve access to it.

Firebase also has a add() and set() methods for data mutation.
add() requires a collection reference

import { doc, setDoc } from "firebase/firestore"; 

await setDoc(doc(db, "cities", "new-city-id"), data);
Enter fullscreen mode Exit fullscreen mode

set() requires a document reference which also requires the doc id, which is what am using since am generating my own ids

import { doc, setDoc } from "firebase/firestore"; 

const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
Enter fullscreen mode Exit fullscreen mode

another tripping point is date and firestore timestamps

export interface tyme{
  nanoseconds: number,
  seconds:number
}
Enter fullscreen mode Exit fullscreen mode

so i made a wrapper function to convert them before rendering them to avoid the " objects acnnot be react children error"

export const toTyme =(time?:tyme)=>{
  if(time){
    const ty= new Date(
        //@ts-ignore
      time.seconds * 1000 + time.nanoseconds / 1000000

    );
    return dayjs(ty).format("DD/MM/YYYY")
 }  

  return dayjs(new Date()).format("DD/MM/YYYY")

}
Enter fullscreen mode Exit fullscreen mode

and that will be it ,
happy coding

firebase mutaion docs

Discussion (0)