I am trying to build a super simple react blog using Strapi as my cms. I am running into a problem where it is trying to render the data before it finishes fetching it. I am trying to display the post image, but I get an error about the attributes being undefined right here: data.data.attributes.image.data.attributes.formats.large.url
.
Here is the ReactJS code:
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Navbar from "../components/Navbar";
function Post() {
const [data, setData] = useState({});
const { id } = useParams();
console.log("handle", id);
const fetchData = () => {
fetch(`http://localhost:1337/api/posts/${id}?populate=*`)
.then((response) => {
return response.json();
})
.then((d) => {
setData(d);
// console.log(data);
});
};
useEffect(() => {
fetchData();
}, []);
// console.log(data);
return (
<>
<Navbar />
<img
src={
"http://localhost:1337" +
data.data.attributes.image.data.attributes.formats.large.url
}
alt="image"
/>
</>
);
}
export default Post;
Here is the api data retrieved:
{
"data": {
"id": 1,
"attributes": {
"postTitle": "First Post",
"datePosted": "2023-04-25",
"author": "Caleb",
"postText": "# Hello <u>**_World_**</u>\nThis is my very first blog post!\n\n![flower-right.png](http://localhost:1337/uploads/flower_right_b36ad9cd80.png)\n\n",
"createdAt": "2023-04-25T07:20:18.295Z",
"updatedAt": "2023-04-25T08:44:04.003Z",
"publishedAt": "2023-04-25T07:20:19.972Z",
"image": {
"data": {
"id": 1,
"attributes": {
"name": "unsplash_BhfE1IgcsA8-smaller.png",
"alternativeText": "header-image",
"caption": null,
"width": 2560,
"height": 1488,
"formats": {
"thumbnail": {
"name": "thumbnail_unsplash_BhfE1IgcsA8-smaller.png",
"hash": "thumbnail_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 245,
"height": 142,
"size": 57.28,
"url": "/uploads/thumbnail_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d.png"
},
"small": {
"name": "small_unsplash_BhfE1IgcsA8-smaller.png",
"hash": "small_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 500,
"height": 291,
"size": 222.62,
"url": "/uploads/small_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d.png"
},
"medium": {
"name": "medium_unsplash_BhfE1IgcsA8-smaller.png",
"hash": "medium_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 750,
"height": 436,
"size": 490.23,
"url": "/uploads/medium_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d.png"
},
"large": {
"name": "large_unsplash_BhfE1IgcsA8-smaller.png",
"hash": "large_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 1000,
"height": 581,
"size": 884.48,
"url": "/uploads/large_unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d.png"
}
},
"hash": "unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d",
"ext": ".png",
"mime": "image/png",
"size": 1276.78,
"url": "/uploads/unsplash_Bhf_E1_Igcs_A8_smaller_b163301e2d.png",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2023-04-25T06:28:01.746Z",
"updatedAt": "2023-04-25T06:35:42.822Z"
}
}
}
}
},
"meta": {}
}
data
is initialized to an empty object:
const [data, setData] = useState({});
Then you try to read a variety of nested properties on that empty object:
<img
src={
"http://localhost:1337" +
data.data.attributes.image.data.attributes.formats.large.url
}
alt="image"
/>
This will clearly fail because {}
has no property called data
, so that property is undefined
, and you can't read the property attributes
(or any property) on undefined
.
You can perhaps use optional chaining:
<img
src={
"http://localhost:1337" +
data.data?.attributes?.image?.data?.attributes?.formats?.large?.url
}
alt="image"
/>
Though keep in mind that this is likely to show a broken image in the UI. Alternatively, if you don't want to show the image at all when data isn't available, check if data is available before showing the image:
{ data.data ? <img
src={
"http://localhost:1337" +
data.data?.attributes?.image?.data?.attributes?.formats?.large?.url
}
alt="image"
/> : null }
(You could replace the explicit null
with a default image, a loading spinner, or nothing at all if you prefer.)
You could slightly simplify the check by initializing to undefined
:
const [data, setData] = useState();
And then checking the object itself instead of a property on it:
{ data ? <img
src={
"http://localhost:1337" +
data.data?.attributes?.image?.data?.attributes?.formats?.large?.url
}
alt="image"
/> : null }