Reuno92
Reuno92

Reputation: 57

React-Native async Array.map = undefined is not an Object

I try to create my first hybrid App with ReactNative. I have an issue with my Array.map…

class HomeScreen extends React.Component {

  state = {
    isLoading: false,
    captured: false,
    wished: false,
    exchanged: false,
    data: {}
  };

  async getPokemonFromApiAsync() {
    try {
      this.setState({isLoading: true});
      let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
return this.setState({
        isLoading: false,
        data: await response.json()
       });
    } catch (error) {
      console.error(error);
  }

  (...)    

  componentWillMount() {
      this.getPokemonFromApiAsync()
  }

  (...)

  result = (result = this.state.data.results) => {
      console.log('test', this.state.data);
      return (
         <View>

             (...)

             result.map( (item, index) => {

             (...)

             }
         </View>
      )
  } 
}

I don't understand, why my function getPokemonFromApiAsync is empty. iOS Simulator returns a TypeError: Undefined is not an object (evaluating 'result.map')

And when adding a constructor like:

constructor(props) {
   super(props)
   this.getPokemonFromApiAsync = This.getPokemonFromApiAsync.bind(this)
}

I have an many errors in console:

Warning: Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the %s component., setState, HomeScreen

For me, it's normal…

What is a good lifecycle for an asynchronous Http request?

Upvotes: 0

Views: 2031

Answers (4)

Andrew
Andrew

Reputation: 28539

Your error is caused by how you have set up your initial data in state.

You have set it up as:

state = {
  isLoading: false,
  captured: false,
  wished: false,
  exchanged: false,
  data: {} // <- here you define it as an object, with no parameters
};

You should be setting it as an object with a results parameter`. So your initial state should look like

state = {
  isLoading: false,
  captured: false,
  wished: false,
  exchanged: false,
  data: { results: [] } // <- here you should define the results inside the object
};

The reason you are getting the error:

TypeError: Undefined is not an object (evaluating 'result.map')

Is because on the initial render, before your fetch response has come back, it is trying to map over this.state.data.results which doesn't exist. You need to make sure that there is an initial value for results in the state.

That should stop the initial error, however you will have to make sure that what you are saving into state for data is also an array, otherwise you will continue to get the same error.


componentWillMount has been deprecated and you should be using componentDidMount.

Also as you are calling an async function inside you componentWillMount you should refactor it in the following way:

async componentDidMount() {
  await this.getPokemonFromApiAsync()
}

So that the mounting doesn't occur until the fetch request has been completed.


I would also refactor your getPokemonFromApiAsync so that you get the response.json() before trying to set it into state. You also don't need the return statement as this.setState doesn't return anything.

async getPokemonFromApiAsync() {
  try {
    this.setState({isLoading: true});
    let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
    let data = await response.json(); // get the data 
    this.setState({
      isLoading: false,
      data: data // now set it to state
    });
  } catch (error) {
    console.error(error);
}

Snack:

Here is a very simple snack showing the code working https://snack.expo.io/@andypandy/pokemon-fetch

Code for snack:

export default class App extends React.Component {

  state = {
    isLoading: false,
    captured: false,
    wished: false,
    exchanged: false,
    data: { results: [] } // <- here you should define the results inside the object
  };

  getPokemonFromApiAsync = async () => {
    try {
      this.setState({ isLoading: true });
      let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
      let data = await response.json(); // get the data
      this.setState({
        isLoading: false,
        data: data // now set it to state
      });
    } catch (error) {
      console.error(error);
    }
  }

  async componentDidMount () {
    await this.getPokemonFromApiAsync();
  }

  render () {
    return (
      <View style={styles.container}>
        {this.state.data.results.map(item => <Text>{item.name}</Text>)}
      </View>
    );
  }
}

Upvotes: 0

Vencovsky
Vencovsky

Reputation: 31625

The reason you are getting the error TypeError: Undefined is not an object (evaluating 'result.map') is that you are getting the result from this.state.data.results, but because data is async, at the first time it renders, data is {} (because you set it in the state), so data.result is undefined and you can't use .map() in a undefined.

To solve this, you can check if data.result is not undefined, before render it.

  return (
     <View>

         (...)

         result && result.map( (item, index) => {

         (...)

         }
     </View>
  )

Upvotes: 0

Nil
Nil

Reputation: 395

A better way is to implement your state values when your promise is resolved using "then".

let response = fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20')
  .then((response) => {
     this.setState({
       isLoading: false,
       data: response.json()
     });
  });

Maybe you can process your data (result.map) in the Promise callback and directly insert the result in your component.

And by the way, XHR calls are generally processed in the componentDidMount method.

Upvotes: 0

Tarun konda
Tarun konda

Reputation: 1880

Best way using axios library github link

npm install axios

Finally, weekly downloads are more than 4,000,000+ Github Starts 50,000+

Upvotes: 1

Related Questions