I am not too confident working with Firestore and have trouble with more complex API calls to get data. Usually I use SQL backends in my apps.
For the section that I am working on, I would like to combine three collections to get an array of ToDos with the involved users and the category the current user labelled this ToDo with. Every involved person can label the ToDo like they prefer, which makes things a little more complicated. Broken down the collections are structured as follows.
todo: Firestore Database Document
{
title: string,
involved: string[], //user ids
involvedCategory: string[] //category ids mapped by index to involved
}
(I tried to have an array of objects here instead of the two arrays, but it seems I would not be able to query the array for the current user´s ID, like mentioned here, so this is a workaround)
category: Firestore Database Document
{
title: string,
color: string
}
user: Firebase Authentication User
{
uid: string,
displayName: string,
photoURL: string,
...
}
THE GOAL An array of ToDo items like this:
{
id: string,
title: string,
involved: User[],
category?: {
title: string,
color: string
}
}
As I am working with TypeScript, I created an interface to use a converter with. My code looks like this so far:
import {
DocumentData,
FirestoreDataConverter,
WithFieldValue,
QueryDocumentSnapshot,
SnapshotOptions,
query,
collection,
where,
} from 'firebase/firestore'
import { store } from '../firebase'
import { useCollectionData } from 'react-firebase-hooks/firestore'
import { User } from 'firebase/auth'
import { useCategories } from './categories'
import { useAuth } from '../contexts/AuthContext'
interface ToDo {
id: string
title: string
involved: User[]
category?: {
title: string
color: string
}
}
const converter: FirestoreDataConverter<ToDo> = {
toFirestore(todo: WithFieldValue<ToDo>): DocumentData {
return {} //not implemented yet
},
fromFirestore(
snapshot: QueryDocumentSnapshot,
options: SnapshotOptions
): ToDo {
const data = snapshot.data(options)
return {
id: snapshot.id,
title: data.title,
category: undefined, //?
involved: [], //?
}
},
}
export function useToDos() {
const { currentUser } = useAuth()
const { categories } = useCategories() //needed in converter
const ref = query(
collection(store, 'habits'),
where('involved', 'array-contains', currentUser.uid)
).withConverter(converter)
const [data] = useCollectionData(ref)
return {
todos: data,
}
}
Is there any way I can do this? I have a Hook that returns all of the user´s categories, but I obviously can´t call that outside the
useToDos
-Hook. And creating the const in the hook does not help, either, as it results in an infinite re-render.
I know this is a long one, but does anyone have tips how I could approach this? Thanks in advance ^^
UPDATE: I had to make two small adjustments to @ErnestoC ´s solution in case anyone is doing something similar:
First, I changed the calls for currentUser.id
to currentUser.uid
.
Afterwards I got the very missleading Firestore Error: PERMISSION_DENIED: Missing or insufficient permissions
, which made me experiment a lot with my security rules. But that is not where the error originated. Debugging the code line by line, I noticed the category objects resolved by the promise where not correct and had a weird path with multiple spaces at the beginning and the end of their ids. When I removed them before saving them in the promises
array, it worked. Although I do not see where the spaces came from in the first place.
promises.push(
getDoc(
doc(
store,
'categories',
docSnap.data().involvedCategory[userCatIndex].replaceAll(' ', '')
)
)
)