I am developing a MERN app which is an online bookstore. I’m using Axios for fetching data from MongoDB. The data is based on several mongoose models that are connected:
Author model: firstName (string), lastName (string), etc., writtenBooks (Book model) which is an array of objects).
Book model: title (string), author (Author model), etc.
There is a page dynamically displaying all the authors – works great. When you click on one of the authors it takes you to a page displaying all the info about this particular author. All the data about the author – firstName, etc. loads fine, until I try to display writtenBooks (which is info from a different model) – then the app breaks, page turns blank.
The error I see in the console is: ‘author.writtenBooks is undefined’
I’ve seen that there are questions with similar problems and I gathered that the issue is probably the fact that axios is asynchronous so the page loads before the data is fetched and that’s why author returns undefined, but I don’t know what I need to change in my code to make it work. I tried many solutions that helped others, but nothing worked for me.
Can you please help me? 🙂
I’ll just add that author.writtenBooks logs in the console:
// Array(8) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…} ]
- I can see it when I comment out ‘{author.writtenBooks.map…}’ from the inside of the return section.
Also, all the writtenBooks appear on the page – just as I want them to – if I uncomment that part AFTER loading the page, but the page breaks as soon as I refresh it.
Here’s the screenshot of the Author data – just to show you that writtenBooks have been populated:
export default function AuthorPage() {
const { id } = useParams()
const [author, setAuthor] = useState({});
useEffect(() => {
async function getData() {
const response = await axios.get(`/api/authors/${id}`);
setAuthor(response.data);
}
getData();
}, []);
console.log(author.writtenBooks)
return (
<>
<SectionTitle title={`${author.firstName} ${author.lastName}`} link="/allauthors" btn="Go Back" />
<section className='AuthorPage margins mt2'>
<h4 className='mt1 mb1'>Books available by this author:</h4>
<ul>
{/* {author.writtenBooks.map((book) => (
<li key={book._id}>→ {book.title}</li>
))} */}
{/* if you uncomment the code above, the data first loads, then breaks after page refresh */}
</ul>
</section>
</>
)
}
Here’s code from the controller:
// /api/authors/
const allAuthors = asyncHandler(async (req, res) => {
const authors = await Author.find({}).sort({ createdAt: -1 })
res.json(authors)
})
// /api/authors/:id
const oneAuthor = asyncHandler(async (req, res) => {
const author = await Author.findOne({ _id: req.params.id })
.populate('writtenBooks').exec()
res.json(author)
})
I’ve tried moving getData function outside of useEffect and then call it from inside of it, but, as expected, it doesn’t change anything.
I also tried using fetch instead of axios, but the result is the same.
>Solution :
Not sure but it seems like the issue is when the component renders for the first time, author is an empty object, and you are trying to access author.writtenBooks, which is not yet defined.
try this code:-
export default function AuthorPage() {
const { id } = useParams();
const [author, setAuthor] = useState({});
useEffect(() => {
async function getData() {
try {
const response = await axios.get(`/api/authors/${id}`);
setAuthor(response.data);
} catch (error) {
console.error("Error fetching author data:", error);
}
}
getData();
}, [id]); // Include id as a dependency
return (
<>
<SectionTitle title={`${author.firstName} ${author.lastName}`} link="/allauthors" btn="Go Back" />
<section className='AuthorPage margins mt2'>
<h4 className='mt1 mb1'>Books available by this author:</h4>
{author.writtenBooks ? (
<ul>
{author.writtenBooks.map((book) => (
<li key={book._id}>→ {book.title}</li>
))}
</ul>
) : (
<p>Loading...</p>
)}
</section>
</>
);
}