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 datauseEffect
changes the state viasetData
- Since state has changed, the component rerenders (runs again). This time the data is there since it has loaded