2
votes

I am trying to access the weather information from an API call to OpenWeatherMap, but I always get a "cannot read property of undefined" error.

In my App.js file I have a CallAPI function that gets passed the users coordinates and fetches the weather data. It's then passed down to my Header component with a prop of "curr".

const [currWeatherData, setCurrWeatherData] = useState({})

useEffect(() => {
  navigator.geolocation.getCurrentPosition(CallAPI, showError);
}, [])

//... showError function

function CallAPI(position){
  const lat = position.coords.latitude
  const long = position.coords.longitude
  fetch(/*api call*/)
    .then(response => response.json())
    .then(data => {
      setCurrWeatherData(data.current)
    })
}

return (
  <div>
    <Header curr = {currWeatherData}/>
  <div>
)

In my Header.js file I am currently just trying to display the weather status.

import React from "react"

function Header(props){
  return(
    <div>
      {/*<h1>{props.curr.weather[0].main}</h1>*/}
      {console.log(props.curr.weather)}
    </div>
  )
}

The beginning of the json file from the API looks like this.

json file

in "current", there is a weather property that is an array with one element which is an object, thus I would assume the correct way to access the "main" property would be "current.weather[0].main". However, I get the error "cannot read property '0' of undefined" when I try to console.log or return that. The strange part is that when I console.log "current.weather" it prints an array with an object to the console.

console

I've tried storing "current.weather" in a variable before accessing its 0th index and I've tried passing "currWeatherData.weather" as the prop in my App.js file, both of which I don't think change anything. I'm not really sure where to go from here, can anyone help?

EDIT: after an hour or so of console.log debugging i figured out my problem. I learned that when using hooks, useState triggers rerenders the same way this.setState did, meaning each time I set the state, it rendered my Header component. I'm guessing the API call didn't finish before rendering it, so the prop was passed as undefined. I solved this by adding an isLoading state and setting it to false after the API call,

//... code above
.then(data => {
  setCurrWeatherData(data.current)
  setIsLoading(false)
})

and in my return, I added a conditional statement

<div>
  {!isLoading && <Header curr = {currWeatherData}/>}
</div>

I skimmed through https://medium.com/swlh/how-does-react-hooks-re-renders-a-function-component-cc9b531ae7f0 to help

2

2 Answers

0
votes

In your App.js you have the following code:

return (
  <div>
    <Header curr = {currWeatherData}/>
  <div>

However, this does not take the async API call into account. Therefore currWeatherData is null because the API call has not yet completed.

You need to consider the lifecycle and only try to render the data after the API call completes. There are various ways in React to do this, depending on your overall app/component design.

There's an example here based on componentDidMount.

0
votes

since you are using a functional components. I suggest you add useEffact(()=> { your api call}, [] ) and this should work. because currently your code is not getting the data you want! hopefully this helps.