Home > Software design >  React error when trying to get data from api using useEffect hook
React error when trying to get data from api using useEffect hook

Time:04-04

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.

  • Related