How can I retrieve an icon from an object/array?

64 views Asked by At

I'm currently working on a project where I need to extract an icon from an object or an array. However, I'm encountering difficulties in doing so. My approach involves storing the icon in a variable and then passing it as a prop to another component. Unfortunately, despite following this approach, the functionality isn't working as I anticipated.

This is the error i'm getting in the console: "Cannot read properties of undefined (reading '0')" That happend when i implement this code <img src={props.icon} alt="cloud" /> with out of that works fine

Here is the code:

import React, { useEffect, useState } from "react";
import Weather from "./components/Weather";
import "./App.css";

function App() {
  const [search, setSearch] = useState("");
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${search}&appid=1769e69d20183ccd0c32ac3215db1d40&units=metric`
    )
      .then((response) => response.json())
      .then((json) => setData(json))
      .catch((error) => console.error(error));
  }, [search]);

  const handleInput = (event) => {
    setSearch(event.target.value);
  };

  let icon = data.weather[0].icon + ".png";

  return (
    <div>
      <Weather icon={icon} inputValue={handleInput} showItems={data} />
    </div>
  );
}

export default App;

import "./style/Weather.css";
import "@fortawesome/fontawesome-free/css/all.css";

function Weather(props) {
  //   const data = [
  //     {
  //       coord: {
  //         lon: -0.1257,
  //         lat: 51.5085,
  //       },
  //       weather: [
  //         {
  //           id: 804,
  //           main: "Clouds",
  //           description: "overcast clouds",
  //           icon: "04d",
  //         },
  //       ],
  //       base: "stations",
  //       main: {
  //         temp: 285.69,
  //         feels_like: 285.19,
  //         temp_min: 284.29,
  //         temp_max: 286.64,
  //         pressure: 1015,
  //         humidity: 84,
  //       },
  //       visibility: 10000,
  //       wind: {
  //         speed: 5.14,
  //         deg: 210,
  //       },
  //       clouds: {
  //         all: 100,
  //       },
  //       dt: 1710850556,
  //       sys: {
  //         type: 2,
  //         id: 2075535,
  //         country: "GB",
  //         sunrise: 1710828286,
  //         sunset: 1710871895,
  //       },
  //       timezone: 0,
  //       id: 2643743,
  //       name: "London",
  //       cod: 200,
  //     },
  //   ];

  return (
    <div className="container">
      <div className="weather-container">
        <input
          onChange={props.inputValue}
          type="text"
          placeholder="Enter a city name"
        />
        <p>{props.showItems.name}</p>
        <img src={props.icon} alt="cloud" />
        {props.showItems.main && <p>{props.showItems.main.temp.toFixed()}°C</p>}
      </div>
    </div>
  );
}

export default Weather;

4

There are 4 answers

1
David On

Cannot read properties of undefined (reading '0')

Presumably this happens here:

let icon = data.weather[0].icon + ".png";

What is data? It's an empty array:

const [data, setData] = useState([]);

Arrays, empty or otherwise, have no property called weather. Did you mean to read the first element of the array? For example:

let icon = data[0].weather[0].icon + ".png";

Though even then you'd need optional chaining, since the array is initially empty:

let icon = data[0]?.weather[0].icon + ".png";

You can include optional chaining any time you are accessing a property/index which may not exist. Keep in mind of course that icon would then be something unexpected, such as "null.png" or "undefined.png".

You could also set it to some default and conditionally override it. For example:

let icon = "default.png";
if (data.length > 0) {
  icon = data[0].weather[0].icon + ".png";
}

Of course, all of this depends entirely on what data actually contains. You initialize it as an array, but then the line of code we're talking about here assumed it was an object. Which is it?

You'll need to know the structure of your data in order to use that data. But any way you look at it, how you use it needs to support both the initial (empty) state and the later state in which the data is populated.

3
ERSİN ÇÖL On

OpenWeatherMap's site explains how to display icons:

https://openweathermap.org/weather-conditions

The following should work:

<img src={`https://openweathermap.org/img/wn/${props.icon}.png`} alt="cloud" />
0
Mr. Polywhirl On

Your weather component should be set up in such a way that is models the response data.When accessing props, you can use object destructuring to access inner-properties.

Here is an example of weather for the city of New York:

{
  "coord": { "lon": -74.006, "lat": 40.7143 },
  "weather": [{
    "id": 804,
    "main": "Clouds",
    "description": "overcast clouds",
    "icon": "04d"
  }],
  "base": "stations",
  "main": {
    "temp": 7.91,
    "feels_like": 5.37,
    "temp_min": 5.9,
    "temp_max": 10.27,
    "pressure": 1006,
    "humidity": 52
  },
  "visibility": 10000,
  "wind": { "speed": 4.12, "deg": 280 },
  "clouds": { "all": 100 },
  "dt": 1710943746,
  "sys": {
    "type": 2,
    "id": 2008101,
    "country": "US",
    "sunrise": 1710932317,
    "sunset": 1710976081
  },
  "timezone": -14400,
  "id": 5128581,
  "name": "New York",
  "cod": 200
}

In the component below, I memoized the <img> elements src attribute. This will update any time the incoming src changes. This is similar to a useEffect.

import { useMemo } from React;

const IMG_PATH = 'https://openweathermap.org/img/wn';

const Weather = (props) => {
  const { data } = props;
  const { name, weather: [{ description, icon, main }] } = data;
  const imgSrc = useMemo(() => `${IMG_PATH}/${icon}.png`, [icon])
  return (
    <div className="weather">
      <h3>{name}</h3>
      <img src={imgSrc} alt={description} />
      <span>{main}</span>
    </div>
  );
};

export default Weather;

Demo

Here is a full (working) demo below.

Just enter a city name into the input and hit Enter or press the "Submit" button.

const { Fragment, useCallback, useEffect, useMemo, useRef, useState } = React;

const API_URL = 'https://api.openweathermap.org/data/2.5/weather';
const API_KEY = '1769e69d20183ccd0c32ac3215db1d40';
const IMG_PATH = 'https://openweathermap.org/img/wn';

const Weather = (props) => {
  const { data } = props;
  const {
    name,
    main: { temp, feels_like, temp_min, temp_max },
    wind: { speed, deg },
    weather: [{ description, icon, main }]
  } = data;
  const imgSrc = useMemo(() => `${IMG_PATH}/${icon}@2x.png`, [icon])
  return (
    <div className="weather flex flex-col items-center justify-center gap-2">
      <h3 className="weather-city text-4xl">{name}</h3>
      <div className="weather-info flex gap-4">
        <div className="weather-temp flex flex-col items-center justify-center">
          <div className="weather-temp-range flex items-center justify-center gap-2 text-sm">
            <span className="flex gap-1">
              LO: 
              <span className="temp">{temp_min}</span>
            </span>
            <span className="flex gap-1">
              HI:
              <span className="temp">{temp_max}</span>
            </span>
          </div>
          <div className="weather-temp-now flex flex-col items-center justify-center gap-2">
            <span className="temp text-5xl">{temp}</span>
            <span className="flex gap-1">
              Like:
              <span className="temp">{feels_like}</span>
            </span>
          </div>
        </div>
        <div className="weather-condition flex flex-col items-center justify-center">
          <img src={imgSrc} alt={description} />
          <span>{main}</span>
        </div>
      </div>
    </div>
  );
};

const SearchForm = (props) => {
  const { searchRef, setWeatherData } = props;
  const [city, setCity] = useState();
  
  useEffect(() => {
    if (city) {
      fetch(`${API_URL}?q=${city}&appid=${API_KEY}&units=metric`)
        .then((response) => response.json())
        .then((data) => setWeatherData(data))
        .catch((error) => console.error(error));
    } else {
      setWeatherData(null);
    }
  }, [city]);

  const searchWeather = useCallback((event) => {
    event.preventDefault(); // Prevent navigation
    setCity(searchRef.current.value);
  }, [searchRef]);
  
  return (
    <form
      className="search-form flex items-center gap-2"
      name="weather-search"
      onSubmit={searchWeather}
    >
      <label className="bold" htmlFor="city-input">City:</label>
      <input
        type="search"
        id="city-input"
        name="city"
        placeholder="Enter a city name"
        ref={searchRef}
      />
      <button type="submit">Search</button>
    </form>
  );
}

const App = () => {
  const searchRef = useRef();
  const [weatherData, setWeatherData] = useState(null);

  return (
    <Fragment>
      <SearchForm
        searchRef={searchRef}
        setWeatherData={setWeatherData}
      />
      <hr className="w-full" />
      {weatherData ? (
        <Weather data={weatherData} />
      ) : (
        <div className="italic">No weather found</div>
      )}
    </Fragment>
  );
};

ReactDOM
  .createRoot(document.getElementById('root'))
  .render(<App />);
.temp:after {
  content: '°C';
}

/* Tailwind classes */
.w-full { width: 100%; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-row { flex-direction: row; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
.text-xs { font-size: 0.75rem; line-height: 1rem; }
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.text-base { font-size: 1rem; line-height: 1.5rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
.text-4xl { font-size: 2.25rem; line-height: 2.5rem; }
.text-5xl { font-size: 3rem; line-height: 1; }
.text-6xl { font-size: 3.75rem; line-height: 1; }
.text-7xl { font-size: 4.5rem; line-height: 1; }
.text-8xl { font-size: 6rem; line-height: 1; }
.text-9xl { font-size: 8rem; line-height: 1; }
.bold { font-weight: bold; }
.italic { font-style: italic; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css" rel="stylesheet"/>
<div id="root" class="flex flex-col items-center justify-center p-4"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

0
Aniket Gund On

In terms to get Icon from data please make following changes in App component

 let icon =  `https://openweathermap.org/img/wn/${data[0].weather[0].icon}.png`;