2

I am facing the error TypeError: Cannot read properties of undefined (reading 'map')

This code should return the name of the city in a card title which is defined in my other file but throwing an error.

Codes:

import React, {Component} from 'react';
import Body from './Body';

class Weather extends Component {
  constructor(props) {
    super(props);
    this.state = {
      weather: [],
    };
  }

  async componentDidMount() {
    const url = `http://api.weatherapi.com/v1/current.json?key=${this.props.api}&q=Jaipur&aqi=no`;
    let data = await fetch(url);
    let parsedData = await data.json();
    this.setState({
      weather: parsedData.weather,
    });
  }

  render() {
    return (
      <div className="container">
        <div className="row">
          {this.state.weather.map((element) => {
            return (
              <div className="col-md-4">
                <Body city={element.location.name} />
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

export default Weather;
3
  • What is the value of parsedData.weather that you are updating this.state.weather to? Is it an array? Commented Sep 29, 2021 at 4:41
  • yes this will store an array of all my weather details fetched from the api Commented Oct 1, 2021 at 5:40
  • Please show us the value of parsedData. From what I can tell, this.state.weather is a defined array on the initial render, so somehow after you fetch data and update state, this.state.weather is no longer defined. This is why you can't read .map or .length and an error is thrown. If I had your this.props.api value I'd just reproduce this myself and see what the response value is. Commented Oct 1, 2021 at 16:13

3 Answers 3

6

The Problem:

your weather array is empty before the API call so using this.state.weather.map will cause the error.

The Solution:

There are two important things before using the map with arrayes:

  1. check for the definition of the array (is the array defined and exists?)
  2. check its length (is the array has some content?)

First

check its declaration/definition by a simple if statement:

{
  if(myArrayOfData) {
    myArrayOfData.map(
      // rest of the codes ...
    )
  }
}

Or with using ? shorthanded of if

{
  myArrayOfData?.map(
    // rest of the codes ...
  )
}

Second

check for the contents of the array and use the map function after checking its length (which tells you the data has arrived from the API call etc. and is ready to process)

{
  if(myArrayOfData) {
    if(myArrayOfData.length > 0) {
     myArrayOfData.map(
        // rest of the codes ...
     )
    }
  }
}

Finally:

while the above snippet works properly, you can simplify it by checking both if conditions together:

{
  if(myArrayOfData?.length > 0) {
     myArrayOfData.map(
        // rest of the codes ...
     )
  }
}

So, simply make some changes in the return of Weather component:

<div className="row">
  {
    if(this.state.weather?.length > 0) {
      this.state.weather.map((element) => {
        return (
          <div className="col-md-4" key={element.id}>  // also don't forget about the passing a unique value as key property
            <Body city={element.location.name}/>
          </div>
        );
      })
    }
  }
</div>

Optional:

In real-world examples, you may need to show some loading components while the data is fetching.

{
  if(myArrayOfData?.length > 0) {
    myArrayOfData.map(
      // rest of the codes ...
    )
  } else {
    <Loading />
  }
}
Be Aware
const anEmptyArray  = []

if(anEmptyArray){
  // rest of the codes ...
}

The result of comparison on if(anEmptyArray) is always true with an empty array.

Sign up to request clarification or add additional context in comments.

Comments

1

the simplest way is just to add "this.state.weather &&" before map method, first it will make sure that "this.state.weather" is defined and then run the code, example is below

{this.state.weather && this.state.weather.map((element) => {
            return (
              <div className="col-md-4">
                <Body city={element.location.name} />
              </div>
            );
})}

Comments

0

Lets make an assumption that parsedData.weather have correct data type. You should make a conditional logic, to check this.state.weather should have a value from API.

Here's an example

 render() {
    const { weather } = this.state; // Access the state `weather`
    return (
        <>
            <div className="container">
                <div className="row">
                     { /* This way use conditional ternary operator */ }
                    {weather.length ? weather.map((element) => {
                        return (
                            <div className="col-md-4">
                                <Body city={element.location.name}/>
                            </div>
                        );
                    }) : <span>Loading...</span>}
                </div>

            </div>
        </>
    );
}

4 Comments

Thanks for the answer but it is raising a error that => TypeError: Cannot read properties of undefined (reading 'length') ;
Yes I know it, but I mean the process is Asynchronous. So that's why the value is undefined
how do fix that ??
You must await asynchronous process, until you get the value. And then you can render the element

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.