I'm trying to create a simple weather web app using an api which contains an input form to search the city and the information to display. And I want to set the default city to a specific city using the useEffect hook. So, as soon as users open the app, the information regarding the city's weather will be displayed. However, it returns an error that says something like the data doesn't exist.
This is my App.js file
import React, { useState, useEffect } from 'react'
import Search from './components/Search.js'
import './App.scss';
function App() {
const [city, setCity] = useState('London')
const [unit, setUnit] = useState('metric')
const [info, setInfo] = useState({})
const key = process.env.REACT_APP_WEATHER_API_KEY
const api = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=${unit}`
useEffect(() => {
fetch(api)
.then(res => {
if (!res.ok){
throw Error('could not fetch the data')
}
return res.json()
})
.then(data => {
setInfo(data)
console.log(data)
})
.catch(err => console.log(err.message))
}, [city])
return (
<div className="app">
<Search setCity={setCity}/>
<h1 className='city'>{city}</h1>
<h2 className='temperature'>Temperature: {info.main.temp}</h2>
</div>
);
}
export default App;
Search.js file
import React from 'react'
function Search({setCity}) {
const search = (e) => {
const loc = e.target.value.trim().replace(/^\w/, (c) => c.toUpperCase())
if (e.key === 'Enter' && loc !== ''){
setCity(loc)
}
}
return (
<div className='search-container'>
<input type="text" onKeyPress={search} defaultValue='London'/>
</div>
)
}
export default Search
Error message
Uncaught TypeError: Cannot read properties of undefined (reading 'temp')
It seems like the error happened because I tried to access the temperature information (info.main.temp) inside the h2 tag. But, if I remove it everything would work normally, and the info would still be retrieved form the api.
CodePudding user response:
You've initialized info
to be an empty object -
const [info, setInfo] = useState({})
The first time your component renders, there's no main
property on the info
object (since it's empty), so this call will always fail with the error you're seeing:
<h2 className='temperature'>Temperature: {info.main.temp}</h2>
To fix it, you could check to see if the property is there before you access it using optional chaining:
<h2 className='temperature'>Temperature: {info.main?.temp}</h2>
CodePudding user response:
Your useEffect depends on "city". In the beginning, you are trying to show the temperature value before any city changes.
To solve this you can avoid trying to show tag:
{info && <h2 className='temperature'>Temperature: {info.main.temp}</h2>}
or:
{info ? <h2 className='temperature'>Temperature: {info.main.temp}</h2> : <Loading/>}
in this situation, you do not need to set a default value to "info" useState.
const [info, setInfo] = useState()
CodePudding user response:
If you check that info.main
is not undefined and then render it out, that will be better
import React, { useState, useEffect } from 'react'
import Search from './components/Search.js'
import './App.scss';
function App() {
const [city, setCity] = useState('London')
const [unit, setUnit] = useState('metric')
const [info, setInfo] = useState({})
const key = process.env.REACT_APP_WEATHER_API_KEY
const api = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=${unit}`
useEffect(() => {
fetch(api)
.then(res => {
if (!res.ok){
throw Error('could not fetch the data')
}
return res.json()
})
.then(data => {
setInfo(data)
console.log(data)
})
.catch(err => console.log(err.message))
}, [city])
return (
<div className="app">
<Search setCity={setCity}/>
<h1 className='city'>{city}</h1>
{info.main ? (
<h2 className='temperature'>Temperature: {info.main.temp}</h2>
) : (
<h2 className='loading'>Loading...</h2>
)}
</div>
);
}
export default App;
CodePudding user response:
you can create an interface for weather API like below.
export interface Coord {
lon: number;
lat: number;
}
export interface Weather {
id: number;
main: string;
description: string;
icon: string;
}
export interface Main {
temp: number;
feels_like: number;
temp_min: number;
temp_max: number;
pressure: number;
humidity: number;
}
export interface Wind {
speed: number;
deg: number;
}
export interface Clouds {
all: number;
}
export interface Sys {
type: number;
id: number;
message: number;
country: string;
sunrise: number;
sunset: number;
}
export interface RootObject {
coord: Coord;
weather: Weather[];
base: string;
main: Main;
visibility: number;
wind: Wind;
clouds: Clouds;
dt: number;
sys: Sys;
timezone: number;
id: number;
name: string;
cod: number;
}
export interface IWeather {
coord: Coord;
weather: Weather;
base: string;
main: Main;
visibility: number;
wind: Wind;
clouds: Clouds;
dt: number;
sys: Sys;
timezone: number;
id: number;
name: string;
cod: number;
}
Then in App.tsx you need to modify 2 things.
const [info, setInfo] = useState<IWeather | null>();
and
<h2 className="temperature">Temperature: {info?.main?.temp}</h2>
you can check working demo
let me know if you have any doubt.