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
1npm i @react-query-firebase/firestore dayjs date-fns
2react-day-picker uniqid
3
4npm i -D @types/dayjs @types/uniqid"
5
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
1const query = useFirestoreQuery(["projects"], ref,{
2subscribe:true
3});
4
5const snapshot = query.data;
6
7return snapshot.docs.map((docSnapshot) => {
8 const data = docSnapshot.data();
9 return <div key={docSnapshot.id}>{data.name}</div>;
10});
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
1const query = useFirestoreQueryData(["projects"], ref,{
2subscribe:true
3});
4
5return query.data.map((document) => {
6 return <div key={document.id}>{document.name}</div>;
7});
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
1 const id = uniqid();
2 const ProjectRef = doc(db, "projects", id);
3 const mutationProject = useFirestoreDocumentMutation(
4 ProjectRef,
5 { merge: true },
6 {
7 onMutate: async (newProject) => {
8
9 await queryClient.cancelQueries("projects");
10
11 const previousTodos = queryClient.getQueryData("projects");
12
13
14 queryClient.setQueryData("projects", (old) => [...old, newProject]);
15
16 return { previousTodos };
17 },
18
19 onError: (err, newTodo, context) => {
20
21 queryClient.setQueryData("projects", context.previousTodos);
22 },
23
24 onSettled: () => {
25 queryClient.invalidateQueries("projects");
26 },
27 }
28 );
29
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
`
1import { doc, setDoc } from "firebase/firestore";
2
3await setDoc(doc(db, "cities", "new-city-id"), data);
set() requires a document reference which also requires the doc id, which is what am using since am generating my own ids
1import { doc, setDoc } from "firebase/firestore";
2
3const cityRef = doc(db, 'cities', 'BJ');
4setDoc(cityRef, { capital: true }, { merge: true });
another tripping point is date and firestore timestamps
1export interface tyme{
2 nanoseconds: number,
3 seconds:number
4}
so i made a wrapper function to convert them before rendering them to avoid the " objects acnnot be react children error"
1export const toTyme =(time?:tyme)=>{
2 if(time){
3 const ty= new Date(
4
5 time.seconds * 1000 + time.nanoseconds / 1000000
6
7 );
8 return dayjs(ty).format("DD/MM/YYYY")
9 }
10
11 return dayjs(new Date()).format("DD/MM/YYYY")
12
13}
and that will be it , happy coding
firebase mutaion docs