6

I'm making an API call in via axios in React with a UseEffect.
We set the response to a variable called data using useState

const [data, setData] = useState({});
  setData(response);

The response is from NASA API and we only get one object (pasted below) returned for this call.

Since I named the response "data" and it has a "data" key as well, if I want to log the url, I know I would type console.log(data.data.url) and that works smoothly in my app.js main function. In my card.js component, I can successfully log console.log(data) and console.log(data.data) and it gives exactly what you would expect, but when I console.log(data.data.url) or (data.data.title) it becomes for some reason undefined, so then this results in a big error in the return function of JSX and the site won't load:

 TypeError: Cannot read property 'data' of undefined error.

I don't think I'm doing anything wrong with my naming as it works fine at higher levels in the object e.g. console.log(data.data) works and I see before my eyes the next level properties listed.

I am literally console.logging this:

{console.log('FROM INSIDE THE RETURN')}
{console.log(props.data)}  // works, displays object {}
{console.log(props.data.data)}  //works, displays object one level lower   
{console.log(props.data.data.url)}  // type error. You name the property.

Needless to say this doesn't work, which was my first approach to the assignment:

<img src={props.data.data.url}/>

That said we got the program working with help of the team lead by shaving off the top layer of the object upstream as follows:

SetData(response.data)

// as opposed to 
SetData(response)

// and then using 
<img src={props.data.url}/>

So we didn't have to reach to the bottom in the props, but for clarity, I want to know why and what difference it makes to the compiler, particularly when it worked fine up to n-1 layers, where n is the number of layers of the object.

I even changed the name of one of the data variables so 'data' wasn't duplicated and the behavior was the same.

Thank you for your help and insights! I really appreciate any insights you can share as well as feedback on my question.

Here is the object I'm working with.

     {
        data: {
            copyright: "Bryan Goff",
            date: "2020-03-18",
            explanation: "What's happening behind...[truncated]...Florida, USA.",
            hdurl: "https://apod.nasa.gov/apod/image/2003/AntiCrepRays_Goff_3072.jpg",
            media_type: "image",
            service_version: "v1",
            title: "Anticrepuscular Rays over Florida",
            url: "https://apod.nasa.gov/apod/image/2003/AntiCrepRays_Goff_960.jpg"
        },
        status: 200,
        statusText: "OK",
        headers: {
            contenttype: "application/json"
        },
        config: {
            url: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY",
            method: "get",
            headers: {
                Accept: "application/json, text/plain, */*"
            },
            transformRequest: [
                null
            ],
            transformResponse: [
                null
            ],
            timeout: 0,
            xsrfCookieName: "XSRF-TOKEN",
            xsrfHeaderName: "X-XSRF-TOKEN",
            maxContentLength: -1
        },
        request: {}
    }
MwamiTovi
  • 2,425
  • 17
  • 25
gcr
  • 443
  • 2
  • 5
  • 14
  • recreate this issue in minimum steps via https://stackblitz.com/ – Praveen Soni Mar 19 '20 at 04:18
  • [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) may help others better understanding what you are facing – keikai Mar 19 '20 at 04:29
  • My code is here https://github.com/webbuildermn/nasa-photo-of-the-day/tree/Gerald-Ryan (note it is on this branch only) Forked from: https://github.com/LambdaSchool/nasa-photo-of-the-day – gcr Mar 19 '20 at 15:53
  • Here it is on sb: https://stackblitz.com/github/xoriwpoqv The two files to compare are app.js and card.js. Toggle on and off line 13 of card.js and compare the difference in the console. I see now that it calls Card twice. I don't know why or if that has anything to do with the bug. – gcr Mar 19 '20 at 16:26
  • 1
    This is the correct stackblitz link to reproduce the issue https://stackblitz.com/edit/github-rrx7ex – gcr Mar 19 '20 at 16:41

2 Answers2

2

My guess is the api call is taking some time and you are trying to set the values before the api call returns . Please try to use a additional isLoading state to check if the api is still executing

import React from 'react';

const Component = () => {  
const [isLoading,setIsLoading] = useState(true)
const [data, setData] = useState({});

useEffect(()=>{
  setTimeout(()=>fetch('https://jsonplaceholder.typicode.com/users/1')
    .then(response => response.json())
    .then(json => {        
        setData(json)
      setIsLoading(false)        
    }),1000)

},[0])


return(
  isLoading ? 'Loading...' :
    <div>
      <h1>Hello {data.name}!</h1>
      <p>Your username is {data.username}</p>
    </div>
  )
}

export default Component
muddassir
  • 815
  • 9
  • 16
  • If he gets the first level data, the api has responded. – The Fool Mar 19 '20 at 08:45
  • Yes, the API is taking extra time and while I do set the values properly and get a full object back eventually, React does what I call a 'dry run' of the component, which is what throws up the error . isLoading is a clever use of state. I'll try it in the future. I came up with a similar idea for today's task (see link below). Not sure they are exact equivalents, but your's is tidier. See here how ReactJS laughs at me and my clever "if" statement and loops back to redo the call that causes the crash. https://www.youtube.com/watch?v=zaLBycYX1g4&t=113s – gcr Mar 21 '20 at 04:31
  • BTW I'm a student, and today's timed sprint is where it mattered as pass/fail. Different task, same issue. As long as the data comes in and the site loads, React can do what it wants, but I had a critical error for a while, causing me to panic. I needed to map an array. Since I set the data as empty obj {} (JS loosely typed), I had to figure out this was the issue, not my main code. I'm still new to React/JS so I was looking in my functions for errors. If I wouldn't have posted yesterday I might have failed. Changing {} to [] solved it today as [] can be mapped, preventing crash. – gcr Mar 21 '20 at 04:59
2

This is indeed an interesting challenge.
Let's do step-by-step analysis and see if we'll agree:

// this initializes `data = {}` when the app first launches
const [data, setData] = useState({});

// Chances are, you are using this within the "useEffect"
// If so at that point, the above `data = response`
setData(response)

You are most likely making the axios NASA API call within the useEffect.
So then, let's narrow down to the API call.

API calls are often asynchronous (non-blocking).
In other words, this data fetching process doesn't block your client-side from doing other "activities". With that out of the way, let square back to your shared code:

Explanation 1: It might be an occurrence while we are fetching data

// works, because initially "data = {}"
{console.log(props.data)}

// works, displays object one level lower
{console.log(props.data.data)}
// Explaining this...
// APIs are often backend apps that query a database for actual data. 
// This returned data is stored in "literals" (often arrays/lists/objects).

// type error. You name the property.
{console.log(props.data.data.url)}
// Based on the above explanation, 
// despite the second `data` being an Object literal, 
// "url" isn't yet defined since the API is still "querying" the database

Explanation 2: It might be a namespace conflict

// If all is fine based on "explanation 1", 
// then this could be a "namespace" conflict during compilation.

// At compilation, JS finds two variables named "data"
// 1. The initial data value, 
   data = {}
// 2. The returned data key,
   {
     data: {...},
   }
// If we had a returned response as follows:
   results = {
     data: {...},
   }
// we probably would have something like this working 
{console.log(response.data.result.data.url)}

// And this might explains why these work...
{console.log(response.data.url)}
<img src={props.data.url}/>

Remember, we are dealing with stubborn JavaScript here.
And that's possibly why many big Reactjs projects increasing now involve TypeScript.

MwamiTovi
  • 2,425
  • 17
  • 25
  • What are you talking mate? Please remove this answer, It's not helpful and its certainly not a definite answer. – The Fool Mar 19 '20 at 08:42
  • @TheFool, kindly give a better answer/explanation. We are simply trying to get our minds together and figure out what's happening at `javascript compilation`. Thanks. – MwamiTovi Mar 19 '20 at 08:50
  • My explanation couldn't count as a simple response. It is clear that our friend who asked already has an answer. And he simply wants an explanation why his change worked. And that's why am giving him two clear explanations on why his first approach failed. – MwamiTovi Mar 19 '20 at 09:08
  • 1
    I might know what's happening, related to #1 above. View my code: https://stackblitz.com/edit/github-rrx7ex Open app.js and Card.js. Toggle line 13 in Card.js and compare in console. Fix line 33 of App.js to see app work overall. I see Card being called twice. Even with line 13 toggled off, console.log(props.data.data) still logs undefined the first time around, so accessing a property of an undefined may be the crash producing error. I still don't know why Card is called twice though, and whether we just get lucky in this instance with our solution or can we rely on it going forward – gcr Mar 19 '20 at 16:56
  • 1
    I forked the og, so I could mess around: [Forked SB](https://stackblitz.com/edit/github-rrx7ex-tkb722) Turns out the crash is due to accessing properties of undefined from the first call to Card. Why it's calling Card twice is the real question now but it does seem to be a common issue for people. If the untimely call can be stopped or blocked, that would probably resolve the issue. I did actually got the code working under the original plan by using if statements and variables in Card.js to prevent logging or rendering what was undefined. See the fork above. – gcr Mar 19 '20 at 18:32