Tech stack: Next.js, React, MongoDB, Mongoose, Zustand (global state)
I am saving objects (Flashcards in JSON format) in MongoDB and fetch the data through Mongoose. I use Mongoose's idea of Model and Schema.
I want to load all data from MongoDB into a global state variable (Zustand) called mainList
conditionally with useEffect
, as soon as the user arrives at the main page. This way I want to minimize the overall communication with MongoDB and also for quicker conditional renderings on various pages. If this makes sense... I don't know, as I am still a beginner.
useVocabStore
is the global hook I created, which holds the variable mainList
, including other functions like fetchAllCards
, which I use to async pull data via getAllFlashcards
from MongoDB, into said global state variable mainList
.
Problem
Within useEffect
I am making an async function call of fetchAllCards
, because ultimately this calls MongoDB. Unfortunately I am getting this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'Flashcard')
My understanding of this error is that when fetchAllCards
calls MongoDB, the data it receives is for some reason undefined.
git@github.com:j-eick/vocab-dojo.git
Question
Why does that async call of MongoDB via global state hook within useEffect cause undefined? What am I missing here?
This my useVocabStore
- hook:
import create from 'zustand';
import {getAllFlashcards} from '../services/vocabServices';
const vocabStore = set => ({
mainList: [],
// pulls cards from MongoDB
fetchAllCards: async () => set({mainList: await getAllFlashcards()}),
addVocab: newCard =>
set(state => ({
mainList: [...state.mainList, newCard],
})),
// other code...
});
export const useVocabStore = create(vocabStore);
useEffect calls MongoDB initially for one time only. Apparently useEffect cannot make an async call (source), which is why I am making an async call within useEffect.
useEffect(() => {
const fetchCards = async () => {
await fetchAllCards();
};
fetchCards().catch(console.error);
}, []);
To briefly touch the otherwise functioning getAllFlashcards
. There are other pages in this next.js project where I also async call getAllFlashcards
without any problems:
//other imports...
import {getAllFlashcards} from '../../services/vocabServices';
export async function getStaticProps() {
const allFlashcards = await getAllFlashcards();
return {
props: {allFlashcards},
};
}
export default function VocabListPage({allFlashcards}) {
//code...
This is the complete code of the main page:
import Head from 'next/head';
import {useEffect} from 'react';
import Layout from '../components/Layout';
import StartSession_noCards from '../components/Links/StartSessionLink_noCards';
import StartSession_withCards from '../components/Links/StartSessionLink_withCards';
import InfoModalPopupSmall from '../components/Modals/InfoModalPopup/InfoModalPopupSmall';
import Textfield from '../components/Texfield/Textbox';
import TextfieldArea from '../components/TextfieldArea';
import Title from '../components/Title/index';
import {useToggleStore} from '../hooks/useToggleStore';
import {useVocabStore} from '../hooks/useVocabStore';
console.clear();
export default function HomePage() {
const fetchAllCards = useVocabStore(state => state.fetchAllCards);
const cardsInList = useToggleStore(state => state.cardsInList);
const showModal = useToggleStore(state => state.showModal);
const hide_Modal = useToggleStore(state => state.hide_Modal);
const show_Modal = useToggleStore(state => state.show_Modal);
const popupModalHandler = () => {
console.log('test');
show_Modal();
setTimeout(() => {
hide_Modal();
}, 3000);
};
useEffect(() => {
console.log('Test');
const fetchCards = async () => {
await fetchAllCards();
};
fetchCards().catch(console.error);
// console.log(mainList);
}, []);
return (
<Layout>
<Head>
<title key="title">Vocab-Dojo</title>
<meta key="description" name="description" content="This is my project" />
</Head>
{/* ############# INFO MODALs ################ */}
{showModal && (
<InfoModalPopupSmall variant="smallInfoModal">
Create flashcard first
</InfoModalPopupSmall>
)}
{/* ############# TEXTFIELD AREAS ################ */}
<TextfieldArea variant="textarea_hardToRemember">
<Title variant="Textbox_Title">Hard to remember</Title>
<Textfield variant="textbox_mainpage">Example 2</Textfield>
</TextfieldArea>
<TextfieldArea variant="textarea_lastAdded">
<Title variant="Textbox_Title">Last Added</Title>
<Textfield variant="textbox_mainpage">Example</Textfield>
</TextfieldArea>
{/* ############# START SESSION ################ */}
<Title variant="Textbox_Title">Train your brain</Title>
{/* PICK DIFFEREN STARTSESSION BUTTON BASED ON CONDITION, whether CardList is full or empty */}
{cardsInList ? (
<StartSession_withCards
variant="startSession_withCards"
onClick={popupModalHandler}
/>
) : (
<StartSession_noCards variant="startSession_noCards" onClick={popupModalHandler} />
)}
</Layout>
);
}
The Mongoose Flashcard Model:
import mongoose from 'mongoose';
const {Schema} = mongoose;
const flashcardSchema = new Schema(
{
frontTitle: {type: String, Number},
frontDescription: {type: String, Number},
backTitle: {type: String, Number},
backDescription: {type: String, Number},
}
// {collection: 'flashcards'}
);
const Flashcard = mongoose.models.Flashcard || mongoose.model('Flashcard', flashcardSchema);
export default Flashcard;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>