I'm building a story editor where you can write stories and separate them into different chapters and I'm having trouble accessing my subcollection for NextJS's "getStaticPaths" because every document is a UUID.
I have data stored in a Firebase Firestore like this:
stories
├── UUID1 (Story 1)
│ └──chapters
│ └── UUID (Chapter 1)
│ └── UUID (Chapter 2)
│
│
├── UUID2 (Story 1)
│ └──chapters
│ └── UUID (Chapter 1)
│ └── UUID (Chapter 2)
│
Problem
I can't access the chapters subcollection because I can't fetch the UUIDs.
Normally, to access the chapters, I create a useEffect hook to store the UUID into a useState hook so I can send a query like the code below:
const chaptersQ = query(
collection(db, "stories", story.data().id, "chapters")
);
Unfortunately, in trying to create dynamic routing and "getStaticPaths" exist before/outside the main function, so I can't use react hooks.
Here's my current code:
export const getStaticPaths = async () => {
const storyQ = collection(db, "stories");
const queryStory = await getDocs(storyQ);
queryStory.forEach(async (story) => {
const chaptersQ = query(
collection(db, "stories", story.data().id, "chapters")
);
const queryChapters = getDocs(chaptersQ);
return {
paths: (await queryChapters).docs?.map((doc) => ({
params: {
id: `${doc.data().storyID}`,
chapter: `${doc.data().chapter}`,
},
})),
};
});
};
This returns an invalid error, as the paths somehow receive an undefined.
I've spent hours working on this. The closest I've gotten is by declaring a variable with "let" in the root layer of the file and then pushing chapter doc.data into it.
Then using that in the return statement to set the paths and parameters. However, this also returned and undefined error.
Thank you in advance!
The forEach
method does not return a value, so the paths
property will not be set correctly, meaning it will be undefined.
In my opinion there are 2 ways you can get the desired result:
forEach
, you can use the map
function to create an array of paths and then return it.export const getStaticPaths = async () => {
const storyQ = collection(db, "stories");
const queryStory = await getDocs(storyQ);
const paths = await Promise.all(queryStory.map(async (story) => {
const chaptersQ = query(
collection(db, "stories", story.data().id, "chapters")
);
const queryChapters = await getDocs(chaptersQ);
return queryChapters.docs.map((doc) => ({
params: {
id: `${doc.data().storyID}`,
chapter: `${doc.data().chapter}`,
},
}));
}));
return {
paths: paths.flat(),
};
};
This should correctly create the paths array, but you may need to adjust it to your specific use case.
export const getStaticPaths = async () => {
const storyQ = collection(db, "stories");
const queryStory = await getDocs(storyQ);
let paths = []
for (const story of queryStory) {
const chaptersQ = query(
collection(db, "stories", story.data().id, "chapters")
);
const queryChapters = await getDocs(chaptersQ);
for(const chapter of queryChapters.docs) {
paths.push({
params: {
id: `${chapter.data().storyID}`,
chapter: `${chapter.data().chapter}`,
},
});
}
}
return {
paths,
};
};
If I were given the choice of picking which approach to be used I would choose the 1st one. As Promise.all()
gives clean code.