0

Im new to react. I am trying to pull data from an API, and then loop through it and display it. Error : Cannot read property 'map' of undefined.

The API data is coming through, but it seems as if React is calling the looplistings before the data is stored into State.

  constructor () {
super()
this.state = {
  data:'',
}

}

componentWillMount(){
 // Im using axios here to get the info, confirmed data coming in. 

//Updating 'data' state to equal the response data from the api call.

}

loopListings = () => {
   return this.state.data.hits.map((item, i) => {
     return(<div className="item-container" key={i}>
      <div className="item-image"></div>
      <div className="item-details">tssss</div>
      </div>)
    })
  }

  loopListings = () => {
return this.state.data.hits.map((item, i) => {
  return(
    <div className="item-container" key={i}>
      <div className="item-image"></div>
      <div className="item-details">tssss</div>
    </div>)
})


 }


  render () {
    return (
      <div>
          {this.loopListings()}

      </div>
        )
      }
2
  • 1
    Before your data comes in, it's just a string. A string does not have the property hits. You should check it exists before you map over it or set initial state to data: { hits: [] } Commented May 11, 2018 at 1:34
  • Looks like I need to read more on lifecycles. I was under the impression that componentWillMount would run before render. I am updating the empty data state, with the api response data inside the componentWillMount area. Commented May 11, 2018 at 5:49

2 Answers 2

2

The reason you are receiving this error is that your call to the API is happening asynchronously to the react lifecycle methods. By the time the API response returned and persisted into the state the render method has been called for the first time and failed due to the fact you were trying to access an attribute on a yet undefined object.

In order to solve this, you need to make sure that until the API response has been persisted into the state the render method will not try to access that part of the state in your render method or to make sure that if it does there is a valid default state in the constructor:

Solve this by changing your render to do something like this:

render () {
    return (
      <div>
      {this.state.data && 
        Array.isArray(this.state.data.hits) 
         && this.loopListings()}
      </div>
        )
      }

or initialize your constructor like so :

constructor () {
      super()
      this.state = {
       data: {hits: []},
      }
    }

Remeber react is just javascript and its behavior is just the same.

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

1 Comment

Thanks for clarifying so well. Answer #1 is essentially a boolean that will run once all is true. Answer #2 : My listings are loaded empty in this situation because of async, and then that re-renders as the state change is complete from the axios call, now showing my listings?
1

You could check if desir data.hits exists inside state.

{this.state.data && Array.isArray(this.state.data.hits) ? 
     this.loopListings()
     : null}

Also make sure that, after retrieving a data cal this.setState method like below.

this.setState({ data })

1 Comment

You can get rid of the extra length check by checking the type (and get rid of the null and just use &&).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.