I have issues with my react page, where its article ID is read from url, and where the content then is fetched from my backend API based on this id. But the page won't work, showing this error in the console:
Uncaught TypeError: Cannot read properties of undefined (reading 'attributes')
Example URL:
http://localhost:1337/api/project/1
I am getting the ID from URL with this line:
const { id } = useParams();
and writing out { id } actually shows the number from the url. So I assume this part works as intended.
My route for this looks like this:
<Route path="/project/:id" element = { <Project /> } />
Futhermore, I am fetching the content from API with this line:
fetch("http://localhost:1337/api/projects/"+id, { headers, method: 'GET' })
But when I am trying to use this ID to fetch the correct article from the backend, it won't work properly.
console.log shows first undefined a couple of times, before actually listing the correct content (five times, though, not one as I thought it should be).
From console.log(project)
undefined
VM327 installHook.js:2116 undefined
bundle.js:2870 undefined
VM327 installHook.js:2116 undefined
bundle.js:2870 {id: 1, attributes: {…}}
VM327 installHook.js:2116 {id: 1, attributes: {…}}
bundle.js:2870 {id: 1, attributes: {…}}
VM327 installHook.js:2116 {id: 1, attributes: {…}}
bundle.js:2870 {id: 1, attributes: {…}}
VM327 installHook.js:2116 {id: 1, attributes: {…}}
The API scheme looks like following:
// 20230401201253
// http://localhost:1337/api/projects/1
{
"data": {
"id": 1,
"attributes": {
"Title": "Resort number one,
"Projecttext": "Text about project",
"createdAt": "2023-03-12T21:27:11.674Z",
"updatedAt": "2023-03-12T21:27:17.222Z",
"publishedAt": "2023-03-12T21:27:17.220Z"
}
},
"meta": {
}
}
And here is the full demo code (I have removed a lot of bootstrap content, but left the essential parts). The kind of data I'm trying to get to work, is like this (and applying this in the code, makes the page load blank with the mentioned undefined error):
{project.attributes.Title}
{project.attributes.Projecttext}
// etc
import React from 'react';
import ReactDOM from 'react-dom/client';
import { useEffect, useState } from 'react';
import { Link } from "react-router-dom";
import { useParams } from 'react-router-dom';
// Parses the JSON returned by a network request
const parseJSON = (resp) => (resp.json ? resp.json() : resp);
// Checks if a network request came back fine, and throws an error if not
const checkStatus = (resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp;
}
return parseJSON(resp).then(resp => {
throw resp;
});
};
const headers = { 'Content-Type': 'application/json' };
const ProjectOne = () => {
const [error, setError] = useState(null);
const [project, setProject] = useState();
// fetch id from url
const { id } = useParams();
useEffect(() => {
fetch("http://localhost:1337/api/projects/"+id, { headers, method: 'GET' })
.then(checkStatus)
.then(parseJSON)
.then(({ data }) => setProject(data))
.catch((error) => setError(error))
}, [])
if (error) {
// Print errors if any
return <div>An error occured: {error.message}</div>;
}
return (
<>
// This id works
{ id }
<div className="app">
// this writes out a couple of nulls, before it writes out the intended "project" array
{ console.log(project) }
<div key={id}>
// this doesn't work, and makes the page load blank, with null-errors in the console
<h3>{project.attributes.Title}</h3>
</div>
</div>
</>
);
}
export default ProjectOne;
I believe I am missing something essential here, but I'm not quite sure where to go next. Any help would be very much appreciated.
const [project, setProject] = useState();
You've set the initial state to be undefined. That's a normal thing to do, but you need to expect it in the rest of your component. If you try to access project.attributes.Title
without checking for undefined, you will get an exception.
So depending on what you want to show while it's still loading, you can either replace the entire output:
if (error) {
// Print errors if any
return <div>An error occured: {error.message}</div>;
}
if (!project) {
return <div>Loading...</div>
// Or to render nothing:
// return null;
}
// The rest of your code can stay the same
return (
<>
...
</>
)
Or just replace the parts of the ui that need the data:
return (
<>
{id}
<div className="app">
<div key={id}>
<h3>{project ? project.attributes.Title : "Loading..."}</h3>
</div>
</div>
</>
);