Search code examples
javascriptreactjsjsonjsxresponse

Cant access second child object of an object api response in react


I cant access the second child object of an object which was generated through an api response. This is the weather api i am using https://www.weatherapi.com/. Look under Display.js for more explanation.

App.js

import React,{useState} from 'react';
import Navbar from './Navbar.js';
import Display from './Display.js'
function App() {
  const[placeName,setPlaceName] = useState('Raipur')
  let key = 'not gonna tell';

  return (
   <>
   <Navbar setPlaceName={setPlaceName} />
   <Display key={key} placeName={placeName} />
   </>
  );
}

export default App;

Navbar.js

import React from 'react';

function Navbar(props) {
  return(
   <>
    <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
  <div className="container-fluid">
    <a className="navbar-brand" href="/">Navbar</a>
    <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="/navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span className="navbar-toggler-icon"></span>
    </button>
    <div className="collapse navbar-collapse" id="navbarSupportedContent">
      <ul className="navbar-nav me-auto mb-2 mb-lg-0">
        <li className="nav-item">
          <a className="nav-link active" aria-current="page" href="/">Home</a>
        </li>
        <li className="nav-item">
          <a className="nav-link" href="/">Link</a>
        </li>
        <li className="nav-item dropdown">
          <a className="nav-link dropdown-toggle" href="/" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul className="dropdown-menu" aria-labelledby="navbarDropdown">
            <li><a className="dropdown-item" href="/">Action</a></li>
            <li><a className="dropdown-item" href="/">Another action</a></li>
            <li><hr className="dropdown-divider"/></li>
            <li><a className="dropdown-item" href="/">Something else here</a></li>
          </ul>
        </li>
        <li className="nav-item">
          <a className="nav-link disabled">Disabled</a>
        </li>
      </ul>
      <form className="d-flex">
        <input className="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
        <button onClick={props.setPlaceName} className="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>
   </>
  );
}

export default Navbar;

Display.js

import React,{useState,useEffect} from 'react';

function Display(props) {
  
  const[weatherInfo,setWeatherInfo] = useState([]);
  
  const getWeatherInfo = async () =>{
    let url =`https://api.weatherapi.com/v1/current.json?key=${props.key}&q=${props.placeName}&aqi=no`;
    let weatherInfo = await fetch(url);
    let parsedweatherInfo = await weatherInfo.json();
    setWeatherInfo(parsedweatherInfo.location);
  }
  // eslint-disable-next-line
  useEffect(async () =>{
    getWeatherInfo();
  },[])
  return (
    <>
    <div className="container">
    <div className="row"> 
    {Object.values(weatherInfo).map((key,value)=>{
    return(
     <div className="col" key={key}>
     {key} 
     </div>
    )  
    })} 
    </div>
    </div>
    </>
  )
}

export default Display;

Here, when i try to parse the current object of the object given below through the function getWeatherInfo, i get an error saying Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead. but when i try to parse the location object, it works. For me the current object is more important than the location.

Example response

{
    "location": {
        "name": "London",
        "region": "City of London, Greater London",
        "country": "United Kingdom",
        "lat": 51.52,
        "lon": -0.11,
        "tz_id": "Europe/London",
        "localtime_epoch": 1631360600,
        "localtime": "2021-09-11 12:43"
    },
    "current": {
        "last_updated_epoch": 1631359800,
        "last_updated": "2021-09-11 12:30",
        "temp_c": 21.0,
        "temp_f": 69.8,
        "is_day": 1,
        "condition": {
            "text": "Partly cloudy",
            "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png",
            "code": 1003
        },
        "wind_mph": 11.9,
        "wind_kph": 19.1,
        "wind_degree": 250,
        "wind_dir": "WSW",
        "pressure_mb": 1017.0,
        "pressure_in": 30.03,
        "precip_mm": 0.0,
        "precip_in": 0.0,
        "humidity": 64,
        "cloud": 50,
        "feelslike_c": 21.0,
        "feelslike_f": 69.8,
        "vis_km": 10.0,
        "vis_miles": 6.0,
        "uv": 5.0,
        "gust_mph": 10.5,
        "gust_kph": 16.9
    }
}

Solution

  • In your App.js component you are using key keyword for prop name. This keyword is used by React in its reconciliation algorithm. It is generally used while rendering an array of elements.

    If you console.log(props) you could actually see that key prop is not in the props object. When you make the request, it returns this object, instead of what you expect it to return:

    {
     error: {
      code: 1002,
      message: "API key is invalid or not provided."
     }
    }
    

    And when you iterate over it inside your Display.js component, you are assigning the object as a React child.

    So try to use different name for the prop, something like api_key.

    UPDATE:

    current object has an object inside, therefore React throws an error that you've mentioned. What you could do is inside your map function, check if key is an object and then parse that object as well. Something like this might work:

    {Object.values(weatherInfo).map((key, value) => {
         let result;
         if (typeof key === "object") {
             result = Object.values(key).join(" ");
         } else {
             result = key;
         }
         return (
             <div className="col" key={value}>
                 {result}
             </div>
         );
    })}`
    

    Also, you could check this answer as well