Reputation: 45
I am having a little problem and can't seem to understand how to fix it. So I am trying to create a pokemon app using pokeapi. The first problem is that I can't get my desired objects to display. For example I want to display {pokemon.abilities[0].ability}, but it always shows Cannot read property '0' of undefined
but just {pokemon.name} or {pokemon.weight} seems to work. So the problem appears when there is more than 1 object in an array.
import React, {Component} from 'react';
export default class PokemonDetail extends Component{
constructor(props){
super(props);
this.state = {
pokemon: [],
};
}
componentWillMount(){
const { match: { params } } = this.props;
fetch(`https://pokeapi.co/api/v2/pokemon/${params.id}/`)
.then(res=>res.json())
.then(pokemon=>{
this.setState({
pokemon
});
});
}
render(){
console.log(this.state.pokemon.abilities[0]);
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}
}
And also some time ago I added the router to my app, so I could pass id to other components, but the thing is I want to display pokemonlist and pokemondetail in a single page, and when you click pokemon in list it fetches the info from pokeapi and display it in pokemondetail component. Hope it makes sense.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom';
import './styles/App.css';
import PokemonList from './PokemonList';
import PokemonDetail from './PokemonDetail';
export default class App extends Component{
render(){
return <div className="App">
<Router>
<div>
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:id" render={(props) => (<PokemonDetail {...props} />)}/>
</div>
</Router>
</div>;
}
}
Upvotes: 0
Views: 838
Reputation: 25516
In case componentWillMount(), An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once.
To handle this we need to set initial state which you have done in constructor but it's not correct. you need to provide default values for the abilities which is an empty array. So change it to
this.state = {
pokemon: {
abilities: []
}
}
And then inside render
method before rendering you need to verify that it's not empty
render() {
return (
(this.state.pokemon.abilities[0]) ?
<div>
{console.log(this.state.pokemon.abilities[0].ability)}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png`} />
</div> :
null
);
}
Upvotes: 1
Reputation: 4078
It is common in React that you always need to safe-check for existence of data before rendering, especially when dealing with API data. At the time your component is rendered, the state is still empty. Thus this.state.pokemon.abilities
is undefined, which leads to the error. this.state.pokemon.name
and this.state.pokemon.weight
manage to escape same fate because you expect them to be string and number, and don't dig in further. If you log them along with abilities
, they will be both undefined at first.
I believe you think the component will wait for data coming from componentWillMount
before being rendered, but sadly that's not the case. The component will not wait for the API response, so what you should do is avoid accessing this.state.pokemon
before the data is ready
render(){
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{!!pokemon.abilities && pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}
Upvotes: 1