javascriptreactjsstrapi

How to grab api before moving on in ReactJS


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": {}
}

Solution

  • 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 }