Home > Software engineering >  I cannot reach the name property in the API
I cannot reach the name property in the API

Time:08-15

I am working on a weather app project. The problem is I cannot reach the name property in the API call.

Here is the API response:

{cod: '200', message: 0, cnt: 40, list: Array(40), city: {…}}
city: {id: 745042, name: 'Istanbul', coord: {…}, country: 'TR', population: 11581707, …}
cnt: 40
cod: "200"
list: (40) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
message: 0
[[Prototype]]: Object

Here is my ApiCall component:

import axios from "axios"
import { useEffect, useState } from "react"

function ApiCall({ getCities }) {
    const[data, setData] = useState({})

    useEffect(() => {
        axios(`https://api.openweathermap.org/data/2.5/forecast?q=Istanbul&appid=c681e6e33ec339728fdf88e0b24a2a01`)
        .then(res => console.log(res.data))
        .catch(err=> console.log(err))
    })

    const { city, list } = data
    console.log(city.name)

Finally, this is the error I get:

Uncaught TypeError: Cannot read properties of undefined (reading 'name')

CodePudding user response:

Since API calls are asynchronous, you need to wait for the response before you assign the value to the state. Thus, you need to move the code inside the then block. Then it will work:

function ApiCall({
  getCities
}) {
  const [data, setData] = useState({})

  useEffect(() => {
    axios(`https://api.openweathermap.org/data/2.5/forecast?q=Istanbul&appid=c681e6e33ec339728fdf88e0b24a2a01`)
      .then(res => {
        const data = res.data;
        const {
          city,
          list
        } = data
        console.log(city.name)
      })
      .catch(err => console.log(err))
  }, [])

Also add an empty array [] to your useEffect hook if you want to load the API on page load.

CodePudding user response:

I suggest you look into how React useState and useEffect work.

Firstly:

The effect didn't run yet

const [data, setData] = useState({}) // This declares the state

// This effect is not run until the component has already rendered.
useEffect(() => {
    axios(`https://api.openweathermap.org/data/2.5/forecast?q=Istanbul&appid=c681e6e33ec339728fdf88e0b24a2a01`)
    .then(res => console.log(res.data))
    .catch(err=> console.log(err))
}, [])
// Pass in an empty array, otherwise there is an infinite loop. 
// Comment if you don't understand why

// Since the effect didn't run yet, the first time around data will be undefined (or rather {} which is the default you set in the `useState`).
const { city, list } = data
console.log(city.name)

// This means, you can't access `data.city.name`, since `data` doesn't even have a `city` property (which is to say `data.city` is undefined, explaining the error message)

This means the first time your function is called (the first render), the data has not loaded yet.

Secondly

Even after you load it, you haven't updated the state:

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

useEffect(() => {
    axios(`https://api.openweathermap.org/data/2.5/forecast?q=Istanbul&appid=c681e6e33ec339728fdf88e0b24a2a01`)
        .then(res => console.log(res.data)) // You're not calling setState
        .catch(err=> console.log(err))

// Since you didn't call setState, all the function does is log it to the console after the render
})

// Even the second time around, since state was not updated, it remains `{}`
const { city, list } = data
console.log(city.name)

The Solution:

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

useEffect(() => {
    axios(`https://api.openweathermap.org/data/2.5/forecast?q=Istanbul&appid=c681e6e33ec339728fdf88e0b24a2a01`)
        .then(res => setState(res.data)) // setState
        .catch(err=> console.log(err))
})

const { city, list } = data
console.log(city?.name) // Should log `undefined` first, then the real result

EDIT: The solution logs undefined first because the first time the component runs, it hasn't fetched the data yet. The order goes:

  • Initial Render (data has not yet loaded)
  • useEffect called after the render. This loads the data
  • useEffect changes the state via setData
  • Since state has changed, the component rerenders (runs again). This time the data is there since it has loaded
  • Related