Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeError: Cannot read property 'data' of undefined - can't access Object "props" beyond certain level in Reactjs

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: {}
    }
like image 401
gcr Avatar asked Mar 19 '20 03:03

gcr


2 Answers

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.

like image 97
MwamiTovi Avatar answered Nov 15 '22 01:11

MwamiTovi


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
like image 2
muddassir Avatar answered Nov 15 '22 01:11

muddassir