reactjs - Hur kan jag mappa data från flera samlingar i snapshot?
Jag är inte så säker på att arbeta med Firestore och har problem med mer komplexa API-anrop för att hämta data. Vanligtvis använder jag SQL-backends i mina appar.
För den sektion som jag arbetar med, skulle jag vilja kombinera tre samlingar för att få en array av ToDos med de involverade användarna och kategorin som den aktuella användaren märkte detta ToDo med. Varje involverad person kan märka ToDo på det sätt de föredrar, vilket gör saker lite mer komplicerat. Nedbrutet är samlingarna strukturerade enligt följande.
att göra: Firestore-databaskommentar
{
title: string,
involved: string[], //user ids
involvedCategory: string[] //category ids mapped by index to involved
}
(Jag försökte ha en array av objekt här istället för de två arrayerna, men det verkar som att jag inte skulle kunna fråga arrayerna efter den aktuella användarens ID, som nämnts här, så detta är en lösning)
kategori: Firestore-databasdokument
{
title: string,
color: string
}
user: Firebase-autentiseringsanvändare
{
uid: string,
displayName: string,
photoURL: string,
...
}
MÅLET En lista med ToDo-objekt som denna:
{
id: string,
title: string,
involved: User[],
category?: {
title: string,
color: string
}
}
Eftersom jag arbetar med TypeScript har jag skapat ett gränssnitt att använda med en omvandlare. Min kod ser hittills ut så här:
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,
}
}
Finns det något sätt jag kan göra detta på? Jag har en Hook som returnerar alla användarens kategorier, men jag kan uppenbarligen inte anropa den utanför useToDos-Hooken. Och att skapa const i hooken hjälper inte heller, eftersom det resulterar i en oändlig re-rendering.
Jag vet att det här är långt, men har någon några tips på hur jag kan ta itu med det? Tack på förhand ^^
UPPDATERING: Jag var tvungen att göra två små justeringar i @ErnestoC:s lösning ifall någon gör något liknande:
Först ändrade jag anropen för currentUser.id till currentUser.uid.
Efteråt fick jag det mycket vilseledande Firestore Error: PERMISSION_DENIED: Missing or insufficient permissions, vilket fick mig att experimentera mycket med mina säkerhetsregler. Men det var inte där felet uppstod. När jag debuggar koden rad för rad, märkte jag att kategorobjekten som löstes av promisen inte var korrekta och hade en konstig sökväg med flera mellanslag i början och slutet av deras id. När jag tog bort dem innan jag sparade dem i promises-arrayen fungerade det. Även om jag inte ser var mellanslagen kom ifrån från början.
promises.push(
getDoc(
doc(
store,
'categories',
docSnap.data().involvedCategory[userCatIndex].replaceAll(' ', '')
)
)
)
Svar #1
Den allmänna metoden, med tanke på att Firestore är en NoSQL-databas som inte stöder serverbaserade JOINS, är att utföra alla datakombinationer på klientsidan eller i backend med en Cloud Function.
För ditt scenario är ett tillvägagångssätt att först fråga ToDo-dokumenten efter medlemskapet av den nuvarande användarens ID i involved-arrayen.
Efteråt hämtar du motsvarande kategoridokument som den nuvarande användaren tilldelat det ToDo (genom att gå efter indexmappningen mellan de två arrayerna). Slutligen bör du kunna konstruera dina ToDo-objekt med datan.
const toDoArray = [];
const promises = [];
//Querying the ToDo collection
const q = query(collection(firestoreDB, 'habits'), where('involved', 'array-contains', currentUser.id));
const querySnap = await getDocs(q);
querySnap.forEach((docSnap) => {
//Uses index mapping
const userCatIndex = docSnap.data().involved.indexOf(currentUser.id);
//For each matching ToDo, get the corresponding category from the categories collection
promises.push(getDoc(doc(firestoreDB, 'categories', docSnap.data().involvedCategory[userCatIndex])));
//Pushes object to ToDo class/interface
toDoArray.push(new ToDo(docSnap.id, docSnap.data().title, docSnap.data().involved))
});
//Resolves all promises of category documents, then adds the data to the existing ToDo objects.
await Promise.all(promises).then(categoryDocs => {
categoryDocs.forEach((userCategory, i) => {
toDoArray[i].category = userCategory.data();
});
});
console.log(toDoArray);
Att använda FirestoreDataConverter-gränssnittet skulle inte vara så annorlunda, eftersom du fortfarande skulle behöva utföra en extra fråga för category-data och sedan lägga till datan i dina egna objekt. Låt mig veta om detta var hjälpsamt.
